chore: switch to a mono repo

This commit is contained in:
rvcas
2022-05-22 12:40:52 -04:00
parent 6ef5dd7e0e
commit 33fee5b3e0
17 changed files with 158 additions and 115 deletions

11
crates/cli/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "neptune"
version = "0.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.57"
clap = { version = "3.1.14", features = ["derive"] }
uplc = { path = '../uplc' }

14
crates/cli/src/lib.rs Normal file
View File

@@ -0,0 +1,14 @@
use std::path::PathBuf;
use clap::Parser;
#[derive(Parser)]
pub struct Cli {
pub input: PathBuf,
}
impl Default for Cli {
fn default() -> Self {
Self::parse()
}
}

15
crates/cli/src/main.rs Normal file
View File

@@ -0,0 +1,15 @@
use uplc::parser;
use neptune::Cli;
fn main() -> anyhow::Result<()> {
let args = Cli::default();
let code = std::fs::read_to_string(&args.input)?;
let program = parser::program(&code)?;
println!("{:#?}", program);
Ok(())
}

8
crates/flat/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "flat"
version = "0.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

34
crates/flat/src/encode.rs Normal file
View File

@@ -0,0 +1,34 @@
use crate::{encoder::Encoder, filler::Filler};
pub trait Encode {
fn encode(&self, e: &mut Encoder) -> Result<(), String>;
}
impl Encode for bool {
fn encode(&self, e: &mut Encoder) -> Result<(), String> {
e.bool(*self);
Ok(())
}
}
impl<T, K> Encode for (T, K)
where
T: Encode,
K: Encode,
{
fn encode(&self, e: &mut Encoder) -> Result<(), String> {
self.0.encode(e)?;
self.1.encode(e)?;
Ok(())
}
}
impl Encode for Filler {
fn encode(&self, e: &mut Encoder) -> Result<(), String> {
e.filler();
Ok(())
}
}

View File

@@ -0,0 +1,78 @@
use crate::encode::Encode;
pub struct Encoder {
pub buffer: Vec<u8>,
// Int
used_bits: usize,
// Int
current_byte: u8,
}
impl Default for Encoder {
fn default() -> Self {
Self::new()
}
}
impl Encoder {
pub fn new() -> Encoder {
Encoder {
buffer: Vec::new(),
used_bits: 0,
current_byte: 0,
}
}
/// Encode any type that implements [`Encode`].
pub fn encode<T: Encode>(&mut self, x: T) -> Result<&mut Self, String> {
x.encode(self)?;
Ok(self)
}
pub fn u8(&mut self, x: u8) -> Result<&mut Self, String> {
todo!()
}
/// Encode a `bool` value.
pub fn bool(&mut self, x: bool) -> &mut Self {
if x {
self.one()
} else {
self.zero()
}
}
fn zero(&mut self) -> &mut Self {
if self.used_bits == 7 {
self.next_word();
} else {
self.used_bits += 1;
}
self
}
fn one(&mut self) -> &mut Self {
if self.used_bits == 7 {
self.current_byte |= 1;
self.next_word();
} else {
self.current_byte |= 128 >> self.used_bits;
self.used_bits += 1;
}
self
}
fn next_word(&mut self) {
self.buffer.push(self.current_byte);
self.current_byte = 0;
self.used_bits = 0;
}
pub(crate) fn filler(&mut self) {
self.current_byte |= 1;
self.next_word();
}
}

13
crates/flat/src/filler.rs Normal file
View File

@@ -0,0 +1,13 @@
pub enum Filler {
FillerStart(Box<Filler>),
FillerEnd,
}
impl Filler {
pub fn len(&self) -> usize {
match self {
Filler::FillerStart(f) => f.len() + 1,
Filler::FillerEnd => 1,
}
}
}

33
crates/flat/src/lib.rs Normal file
View File

@@ -0,0 +1,33 @@
mod encode;
mod encoder;
mod filler;
pub mod en {
pub use super::encode::*;
pub use super::encoder::*;
}
pub fn encode<T>(value: T) -> Result<Vec<u8>, String>
where
T: en::Encode,
{
let mut e = en::Encoder::new();
e.encode((value, filler::Filler::FillerEnd))?;
Ok(e.buffer)
}
#[cfg(test)]
mod test {
#[test]
fn encode_bool() {
let bytes = super::encode(true).unwrap();
assert_eq!(bytes, vec![0b10000001]);
let bytes = super::encode(false).unwrap();
assert_eq!(bytes, vec![0b00000001]);
}
}

14
crates/uplc/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "uplc"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.57"
combine = "4.6.4"
flat = { path = "../flat" }
hex = "0.4.3"
strum = "0.24.0"
strum_macros = "0.24.0"

View File

@@ -0,0 +1,3 @@
(program 1.0.0
[[(builtin addInteger) (con integer 4)] (con integer 8)]
)

50
crates/uplc/src/ast.rs Normal file
View File

@@ -0,0 +1,50 @@
use crate::builtins::DefaultFunction;
#[derive(Debug)]
pub struct Program {
pub version: String,
pub term: Term,
}
#[derive(Debug, Clone)]
pub enum Term {
// tag: 0
Var(String),
// tag: 1
Delay(Box<Term>),
// tag: 2
Lambda {
parameter_name: String,
body: Box<Term>,
},
// tag: 3
Apply {
function: Box<Term>,
argument: Box<Term>,
},
// tag: 4
Constant(Constant),
// tag: 5
Force(Box<Term>),
// tag: 6
Error,
// tag: 7
Builtin(DefaultFunction),
}
#[derive(Debug, Clone)]
pub enum Constant {
// TODO: figure out the right size for this
// tag: 0
Integer(i64),
// tag: 1
ByteString(Vec<u8>),
// tag: 2
String(String),
// tag: 3
Char(char),
// tag: 4
Unit,
// tag: 5
Bool(bool),
}

View File

@@ -0,0 +1,79 @@
use strum_macros::EnumString;
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, EnumString)]
#[strum(serialize_all = "camelCase")]
pub enum DefaultFunction {
// Integer functions
AddInteger,
SubtractInteger,
MultiplyInteger,
DivideInteger,
QuotientInteger,
RemainderInteger,
ModInteger,
EqualsInteger,
LessThanInteger,
LessThanEqualsInteger,
// ByteString functions
AppendByteString,
ConsByteString,
SliceByteString,
LengthOfByteString,
IndexByteString,
EqualsByteString,
LessThanByteString,
LessThanEqualsByteString,
// Cryptography and hash functions
#[strum(serialize = "sha2_256")]
Sha2_256,
Sha3_256,
Blake2b_256,
VerifySignature,
VerifyEcdsaSecp256k1Signature,
VerifySchnorrSecp256k1Signature,
// String functions
AppendString,
EqualsString,
EncodeUtf8,
DecodeUtf8,
// Bool function
IfThenElse,
// Unit function
ChooseUnit,
// Tracing function
Trace,
// Pairs functions
FstPair,
SndPair,
// List functions
ChooseList,
MkCons,
HeadList,
TailList,
NullList,
// Data functions
// It is convenient to have a "choosing" function for a data type that has more than two
// constructors to get pattern matching over it and we may end up having multiple such data
// types, hence we include the name of the data type as a suffix.
ChooseData,
ConstrData,
MapData,
ListData,
IData,
BData,
UnConstrData,
UnMapData,
UnListData,
UnIData,
UnBData,
EqualsData,
SerialiseData,
// Misc constructors
// Constructors that we need for constructing e.g. Data. Polymorphic builtin
// constructors are often problematic (See note [Representable built-in
// functions over polymorphic built-in types])
MkPairData,
MkNilData,
MkNilPairData,
}

6
crates/uplc/src/lib.rs Normal file
View File

@@ -0,0 +1,6 @@
pub mod ast;
pub mod builtins;
pub mod parser;
#[macro_use]
extern crate combine;

270
crates/uplc/src/parser.rs Normal file
View File

@@ -0,0 +1,270 @@
use std::str::FromStr;
use combine::{
attempt, between, choice, many1,
parser::char::{alpha_num, digit, hex_digit, space, spaces, string},
skip_many1,
stream::position,
token, EasyParser, ParseError, Parser, Stream,
};
use crate::{
ast::{Constant, Program, Term},
builtins::DefaultFunction,
};
pub fn program(src: &str) -> anyhow::Result<Program> {
let mut parser = program_();
let result = parser.easy_parse(position::Stream::new(src));
match result {
Ok((program, _)) => Ok(program),
Err(err) => Err(anyhow::anyhow!("{}", err)),
}
}
fn program_<Input>() -> impl Parser<Input, Output = Program>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
let prog = string("program").with(skip_many1(space())).with(
(version(), skip_many1(space()), term().skip(spaces()))
.map(|(version, _, term)| Program { version, term }),
);
between(token('('), token(')'), prog).skip(spaces())
}
fn version<Input>() -> impl Parser<Input, Output = String>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
(
many1(digit()),
token('.'),
many1(digit()),
token('.'),
many1(digit()),
)
.map(
|(major, _, minor, _, patch): (String, char, String, char, String)| {
format!("{}.{}.{}", major, minor, patch)
},
)
}
fn term<Input>() -> impl Parser<Input, Output = Term>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
choice((
attempt(delay()),
attempt(lambda()),
attempt(apply()),
attempt(constant()),
attempt(force()),
attempt(error()),
attempt(builtin()),
))
}
parser! {
fn term_[I]()(I) -> Term
where [I: Stream<Token = char>]
{
term()
}
}
fn delay<Input>() -> impl Parser<Input, Output = Term>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
between(
token('('),
token(')'),
string("delay")
.with(skip_many1(space()))
.with(term_())
.map(|term| Term::Delay(Box::new(term))),
)
}
fn force<Input>() -> impl Parser<Input, Output = Term>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
between(
token('('),
token(')'),
string("force")
.with(skip_many1(space()))
.with(term_())
.map(|term| Term::Force(Box::new(term))),
)
}
fn lambda<Input>() -> impl Parser<Input, Output = Term>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
between(
token('('),
token(')'),
string("lam")
.with(skip_many1(space()))
.with((many1(alpha_num()), skip_many1(space()), term_()))
.map(|(parameter_name, _, term)| Term::Lambda {
parameter_name,
body: Box::new(term),
}),
)
}
fn apply<Input>() -> impl Parser<Input, Output = Term>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
between(
token('['),
token(']'),
(term_().skip(skip_many1(space())), term_()).map(|(function, argument)| Term::Apply {
function: Box::new(function),
argument: Box::new(argument),
}),
)
}
pub fn builtin<Input>() -> impl Parser<Input, Output = Term>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
between(
token('('),
token(')'),
string("builtin")
.with(skip_many1(space()))
.with(many1(alpha_num()))
.map(|builtin_name: String| {
Term::Builtin(DefaultFunction::from_str(&builtin_name).unwrap())
}),
)
}
pub fn error<Input>() -> impl Parser<Input, Output = Term>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
between(
token('('),
token(')'),
string("error")
.with(skip_many1(space()))
.map(|_| Term::Error),
)
}
pub fn constant<Input>() -> impl Parser<Input, Output = Term>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
between(
token('('),
token(')'),
string("con")
.with(skip_many1(space()))
.with(choice((
attempt(constant_integer()),
attempt(constant_bytestring()),
attempt(constant_string()),
attempt(constant_unit()),
attempt(constant_bool()),
)))
.map(Term::Constant),
)
}
fn constant_integer<Input>() -> impl Parser<Input, Output = Constant>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
string("integer")
.with(skip_many1(space()))
.with(many1(digit()))
.map(|d: String| Constant::Integer(d.parse::<i64>().unwrap()))
}
fn constant_bytestring<Input>() -> impl Parser<Input, Output = Constant>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
string("bytestring")
.with(skip_many1(space()))
.with(token('#'))
.with(many1(hex_digit()))
.map(|b: String| Constant::ByteString(hex::decode(b).unwrap()))
}
fn constant_string<Input>() -> impl Parser<Input, Output = Constant>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
string("string")
.with(skip_many1(space()))
.with(between(token('"'), token('"'), many1(alpha_num())))
.map(Constant::String)
}
fn constant_unit<Input>() -> impl Parser<Input, Output = Constant>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
string("unit")
.with(skip_many1(space()))
.with(string("()"))
.map(|_| Constant::Unit)
}
fn constant_bool<Input>() -> impl Parser<Input, Output = Constant>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
string("bool")
.with(skip_many1(space()))
.with(string("True").or(string("False")))
.map(|b| Constant::Bool(b == "True"))
}
#[cfg(test)]
mod test {
use combine::Parser;
const CODE: &str = include_str!("../example/plutus-core");
#[test]
fn parse_program() {
let result = super::program_().parse(CODE);
assert!(result.is_ok());
let program = result.unwrap().0;
assert_eq!(program.version, "1.0.0");
}
}