feat: rename to aiken and add e2e tests for uplc

This commit is contained in:
rvcas 2022-06-11 23:22:24 -04:00
parent 1ef116fcda
commit 984c253f31
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
17 changed files with 9915 additions and 330 deletions

3
.gitignore vendored
View File

@ -1,2 +1 @@
/target /target
*.flat

99
Cargo.lock generated
View File

@ -3,12 +3,12 @@
version = 3 version = 3
[[package]] [[package]]
name = "ahash" name = "aiken"
version = "0.3.8" version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
dependencies = [ dependencies = [
"const-random", "anyhow",
"clap",
"uplc",
] ]
[[package]] [[package]]
@ -40,21 +40,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chumsky"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.1.18" version = "3.1.18"
@ -94,53 +79,14 @@ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
[[package]]
name = "const-random"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f590d95d011aa80b063ffe3253422ed5aa462af4e9867d43ce8337562bac77c4"
dependencies = [
"const-random-macro",
"proc-macro-hack",
]
[[package]]
name = "const-random-macro"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615f6e27d000a2bffbc7f2f6a8669179378fa27ee4d0a509e985dfc0a7defb40"
dependencies = [
"getrandom",
"lazy_static",
"proc-macro-hack",
"tiny-keccak",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "flat" name = "flat"
version = "0.0.0" version = "0.0.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"thiserror", "thiserror",
] ]
[[package]]
name = "getrandom"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.11.2" version = "0.11.2"
@ -190,15 +136,6 @@ version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "neptune"
version = "0.0.0"
dependencies = [
"anyhow",
"clap",
"uplc",
]
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.0.1" version = "6.0.1"
@ -256,12 +193,6 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.39" version = "1.0.39"
@ -357,15 +288,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.0" version = "1.0.0"
@ -374,9 +296,8 @@ checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
[[package]] [[package]]
name = "uplc" name = "uplc"
version = "0.1.0" version = "0.0.1"
dependencies = [ dependencies = [
"chumsky",
"flat", "flat",
"hex", "hex",
"peg", "peg",
@ -391,12 +312,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -1,22 +1,23 @@
# Neptune # AIKEN
Experiments with Plutus Core A cardano smart contract language and toolchain
## Roadmap ## Roadmap
These are generic milestones and the listed ordering These are generic milestones and the listed ordering
is not necessariy the implementation order is not necessariy the implementation order or full scope.
- [ ] compile plutus core into it's on chain encoding - [x] compile plutus core into it's on chain encoding
- [ ] reverse the on chain encoding into plutus core - [x] reverse the on chain encoding into plutus core
- [ ] Plutus Core interpreter
- [ ] create a higher level syntax with inspiration from - [ ] create a higher level syntax with inspiration from
- JS - JS
- ReasonML - ReasonML
- Elm - Elm
- Roc - Roc
- Rust - Rust
- Gleam
- [ ] Language Server - [ ] Language Server
- [ ] Plutus Core interpreter
## Resources ## Resources

View File

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

View File

@ -12,8 +12,16 @@ pub enum Cli {
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum UplcCommand { pub enum UplcCommand {
Flat { input: PathBuf }, Flat {
Unflat { input: PathBuf }, input: PathBuf,
#[clap(short, long)]
print: bool,
},
Unflat {
input: PathBuf,
#[clap(short, long)]
print: bool,
},
} }
impl Default for Cli { impl Default for Cli {

View File

@ -3,14 +3,14 @@ use uplc::{
parser, parser,
}; };
use neptune::{Cli, UplcCommand}; use aiken::{Cli, UplcCommand};
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let args = Cli::default(); let args = Cli::default();
match args { match args {
Cli::Uplc(uplc) => match uplc { Cli::Uplc(uplc) => match uplc {
UplcCommand::Flat { input } => { UplcCommand::Flat { input, print } => {
let code = std::fs::read_to_string(&input)?; let code = std::fs::read_to_string(&input)?;
let program = parser::program(&code)?; let program = parser::program(&code)?;
@ -19,26 +19,28 @@ fn main() -> anyhow::Result<()> {
let bytes = program.to_flat()?; let bytes = program.to_flat()?;
for (i, byte) in bytes.iter().enumerate() { if print {
print!("{:08b}", byte); for (i, byte) in bytes.iter().enumerate() {
print!("{:08b}", byte);
if (i + 1) % 4 == 0 { if (i + 1) % 4 == 0 {
println!(); println!();
} else { } else {
print!(" "); print!(" ");
}
} }
}
println!(); println!();
}
} }
UplcCommand::Unflat { input } => { UplcCommand::Unflat { input, print } => {
let bytes = std::fs::read(&input)?; let bytes = std::fs::read(&input)?;
let program = Program::<FakeNamedDeBruijn>::from_flat(&bytes)?; let program = Program::<FakeNamedDeBruijn>::from_flat(&bytes)?;
let encoded_flat = program.to_flat()?; if print {
println!("{}", encoded_flat.len()); println!("{:#?}", program);
assert!(bytes == encoded_flat) }
} }
}, },
} }

View File

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

View File

@ -1,12 +1,11 @@
[package] [package]
name = "uplc" name = "uplc"
version = "0.1.0" version = "0.0.1"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
chumsky = "0.8.0"
flat = { path = "../flat" } flat = { path = "../flat" }
hex = "0.4.3" hex = "0.4.3"
peg = "0.8.0" peg = "0.8.0"

View File

@ -1,7 +0,0 @@
(program 11.22.33
[
(
lam x (lam x x)) (con string "PT8"
)
]
)

View File

@ -1,7 +1,8 @@
#![recursion_limit = "10000"]
pub mod ast; pub mod ast;
pub mod builtins; pub mod builtins;
mod debruijn; mod debruijn;
mod flat; mod flat;
pub mod parser; pub mod parser;
#[cfg(test)]
mod test;

View File

@ -1,20 +1,31 @@
use std::{collections::HashMap, str::FromStr}; use std::str::FromStr;
use chumsky::{
prelude::{end, filter, just, recursive, Simple},
text::{ident, int, keyword, TextParser},
Parser,
};
use crate::{ use crate::{
ast::{Constant, Name, Program, Term, Unique}, ast::{Constant, Name, Program, Term},
builtins::DefaultFunction, builtins::DefaultFunction,
}; };
use interner::Interner;
use peg::{error::ParseError, str::LineCol};
mod interner;
pub fn program(src: &str) -> Result<Program<Name>, ParseError<LineCol>> {
let mut interner = Interner::new();
let mut program = uplc::program(src)?;
interner.program(&mut program);
Ok(program)
}
peg::parser! { peg::parser! {
grammar parser() for str { grammar uplc() for str {
pub rule program() -> Program<Name> pub rule program() -> Program<Name>
= "(" _* "program" _+ v:version() _+ t:term() _* ")" { Program {version: v, term: t} } = _* "(" _* "program" _+ v:version() _+ t:term() _* ")" _* {
Program {version: v, term: t}
}
rule version() -> (usize, usize, usize) rule version() -> (usize, usize, usize)
= major:number() "." minor:number() "." patch:number() { = major:number() "." minor:number() "." patch:number() {
@ -43,7 +54,9 @@ peg::parser! {
} }
rule builtin() -> Term<Name> rule builtin() -> Term<Name>
= "(" _* "builtin" _+ b:ident() _* ")" { Term::Builtin(DefaultFunction::from_str(&b).unwrap()) } = "(" _* "builtin" _+ b:ident() _* ")" {
Term::Builtin(DefaultFunction::from_str(&b).unwrap())
}
rule var() -> Term<Name> rule var() -> Term<Name>
= n:name() { Term::Var(n) } = n:name() { Term::Var(n) }
@ -77,7 +90,9 @@ peg::parser! {
= "integer" _+ i:number() { Constant::Integer(i as isize) } = "integer" _+ i:number() { Constant::Integer(i as isize) }
rule constant_bytestring() -> Constant rule constant_bytestring() -> Constant
= "bytestring" _+ "#" i:ident()* { Constant::ByteString(hex::decode(String::from_iter(i)).unwrap()) } = "bytestring" _+ "#" i:ident()* {
Constant::ByteString(hex::decode(String::from_iter(i)).unwrap())
}
rule constant_string() -> Constant rule constant_string() -> Constant
= "string" _+ "\"" s:[^ '"']* "\"" { Constant::String(String::from_iter(s)) } = "string" _+ "\"" s:[^ '"']* "\"" { Constant::String(String::from_iter(s)) }
@ -94,195 +109,19 @@ peg::parser! {
rule name() -> Name rule name() -> Name
= text:ident() { Name { text, unique: 0.into() } } = text:ident() { Name { text, unique: 0.into() } }
rule ident() -> String = i:['a'..='z' | 'A'..='Z' | '0'..='9' | '_']+ { String::from_iter(i) } rule ident() -> String
= i:['a'..='z' | 'A'..='Z' | '0'..='9' | '_']+ {
String::from_iter(i)
}
rule _ = [' ' | '\n'] rule _ = [' ' | '\n']
} }
} }
struct ParserState {
identifiers: HashMap<String, Unique>,
current: Unique,
}
impl ParserState {
fn new() -> Self {
ParserState {
identifiers: HashMap::new(),
current: Unique::new(0),
}
}
fn intern(&mut self, text: &str) -> Unique {
if let Some(u) = self.identifiers.get(text) {
*u
} else {
let unique = self.current;
self.identifiers.insert(text.to_string(), unique);
self.current.increment();
unique
}
}
}
pub fn program(src: &str) -> Result<Program<Name>, peg::error::ParseError<peg::str::LineCol>> {
parser::program(src)
}
fn program_() -> impl Parser<char, Program<Name>, Error = Simple<char>> {
keyword("program")
.ignore_then(version().padded())
.then(term())
.map(|(version, term)| Program { version, term })
.delimited_by(just('(').padded(), just(')').padded())
.then_ignore(end())
}
fn version() -> impl Parser<char, (usize, usize, usize), Error = Simple<char>> {
int(10)
.then_ignore(just('.'))
.then(int(10))
.then_ignore(just('.'))
.then(int(10))
.map(|((major, minor), patch)| {
(
major.parse::<usize>().unwrap(),
minor.parse::<usize>().unwrap(),
patch.parse::<usize>().unwrap(),
)
})
}
fn term() -> impl Parser<char, Term<Name>, Error = Simple<char>> {
recursive(|term| {
let delay = keyword("delay")
.ignore_then(term.clone().padded())
.delimited_by(just('(').padded(), just(')').padded())
.map(|t| dbg!(Term::Delay(Box::new(t))));
let force = keyword("force")
.ignore_then(term.clone().padded())
.delimited_by(just('(').padded(), just(')').padded())
.map(|t| dbg!(Term::Force(Box::new(t))));
let lambda = keyword("lam")
.ignore_then(name().padded())
.then(term.clone())
.delimited_by(just('(').padded(), just(')').padded())
.map(|(parameter_name, t)| {
dbg!(Term::Lambda {
parameter_name,
body: Box::new(t),
})
});
let apply = term
.clone()
.padded()
.then(term.clone().padded().repeated())
.delimited_by(just('[').padded(), just(']').padded())
.foldl(|lhs, rhs| Term::Apply {
function: Box::new(lhs),
argument: Box::new(rhs),
});
constant()
.or(builtin())
.or(var())
.or(lambda)
.or(apply)
.or(delay)
.or(force)
.or(error())
})
}
fn constant() -> impl Parser<char, Term<Name>, Error = Simple<char>> {
keyword("con")
.ignore_then(
constant_integer()
.or(constant_bytestring())
.or(constant_string())
.or(constant_unit())
.or(constant_bool()),
)
.delimited_by(just('(').padded(), just(')').padded())
.map(Term::Constant)
}
fn builtin() -> impl Parser<char, Term<Name>, Error = Simple<char>> {
keyword("builtin")
.ignore_then(ident().padded())
.delimited_by(just('(').padded(), just(')').padded())
.map(|builtin_name: String| {
Term::Builtin(DefaultFunction::from_str(&builtin_name).unwrap())
})
}
fn var() -> impl Parser<char, Term<Name>, Error = Simple<char>> {
name().map(Term::Var)
}
fn error() -> impl Parser<char, Term<Name>, Error = Simple<char>> {
keyword("error")
.ignored()
.delimited_by(just('(').padded(), just(')').padded())
.map(|_| Term::Error)
}
fn name() -> impl Parser<char, Name, Error = Simple<char>> {
ident().map(|text| Name {
text,
unique: 0.into(),
})
}
fn constant_integer() -> impl Parser<char, Constant, Error = Simple<char>> {
keyword("integer")
.padded()
.ignore_then(int(10))
.map(|d: String| Constant::Integer(d.parse::<isize>().unwrap()))
}
fn constant_bytestring() -> impl Parser<char, Constant, Error = Simple<char>> {
keyword("bytestring")
.padded()
.ignore_then(just('#'))
.ignore_then(int(16))
.map(|b: String| Constant::ByteString(hex::decode(b).unwrap()))
}
fn constant_string() -> impl Parser<char, Constant, Error = Simple<char>> {
keyword("string")
.padded()
.ignore_then(just('"'))
.ignore_then(filter(|c| *c != '"').repeated())
.then_ignore(just('"'))
.collect::<String>()
.map(Constant::String)
}
fn constant_unit() -> impl Parser<char, Constant, Error = Simple<char>> {
keyword("unit")
.padded()
.ignore_then(just('('))
.ignore_then(just(')'))
.ignored()
.map(|_| Constant::Unit)
}
fn constant_bool() -> impl Parser<char, Constant, Error = Simple<char>> {
keyword("bool")
.padded()
.ignore_then(just("True").or(just("False")))
.map(|b| Constant::Bool(b == "True"))
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::ast::{Constant, Name, Program, Term};
#[test] #[test]
fn parse_program() { fn parse_program() {
let code = r#" let code = r#"
@ -290,12 +129,14 @@ mod test {
(con integer 11) (con integer 11)
) )
"#; "#;
let result = super::program(code); let program = super::program(code).unwrap();
assert!(result.is_ok()); assert_eq!(
program,
let program = result.unwrap(); Program::<Name> {
version: (11, 22, 33),
assert_eq!(program.version, (11, 22, 33)); term: Term::Constant(Constant::Integer(11)),
}
);
} }
} }

View File

@ -0,0 +1,57 @@
use std::collections::HashMap;
use crate::ast::{Name, Program, Term, Unique};
pub struct Interner {
identifiers: HashMap<String, Unique>,
current: Unique,
}
impl Interner {
pub fn new() -> Self {
Interner {
identifiers: HashMap::new(),
current: Unique::new(0),
}
}
pub fn program(&mut self, program: &mut Program<Name>) {
self.term(&mut program.term);
}
pub fn term(&mut self, term: &mut Term<Name>) {
match term {
Term::Var(name) => name.unique = self.intern(&name.text),
Term::Delay(term) => self.term(term),
Term::Lambda {
parameter_name,
body,
} => {
parameter_name.unique = self.intern(&parameter_name.text);
self.term(body);
}
Term::Apply { function, argument } => {
self.term(function);
self.term(argument);
}
Term::Constant(_) => (),
Term::Force(term) => self.term(term),
Term::Error => (),
Term::Builtin(_) => (),
}
}
fn intern(&mut self, text: &str) -> Unique {
if let Some(u) = self.identifiers.get(text) {
*u
} else {
let unique = self.current;
self.identifiers.insert(text.to_string(), unique);
self.current.increment();
unique
}
}
}

41
crates/uplc/src/test.rs Normal file
View File

@ -0,0 +1,41 @@
// e2e encoding/decoding tests
use crate::{
ast::{DeBruijn, Program},
parser,
};
#[test]
fn integer() {
let bytes = include_bytes!("../test_data/basic/integer/integer.flat");
let code = include_str!("../test_data/basic/integer/integer.uplc");
let parsed_program = parser::program(code).unwrap();
let debruijn_program: Program<DeBruijn> = parsed_program.try_into().unwrap();
let decoded_program: Program<DeBruijn> = Program::from_flat(bytes).unwrap();
assert_eq!(debruijn_program, decoded_program);
let encoded_program = debruijn_program.to_flat().unwrap();
assert_eq!(encoded_program, bytes);
}
#[test]
fn jpg() {
let bytes = include_bytes!("../test_data/jpg/jpg.flat");
let code = include_str!("../test_data/jpg/jpg.uplc");
let parsed_program = parser::program(code).unwrap();
let debruijn_program: Program<DeBruijn> = parsed_program.try_into().unwrap();
let decoded_program: Program<DeBruijn> = Program::from_flat(bytes).unwrap();
assert_eq!(debruijn_program, decoded_program);
let encoded_program = debruijn_program.to_flat().unwrap();
assert_eq!(encoded_program, bytes);
}

View File

@ -0,0 +1 @@
!H<05>

View File

@ -0,0 +1,3 @@
(program 11.22.33
(con integer 11)
)

Binary file not shown.

File diff suppressed because it is too large Load Diff