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

1
.gitignore vendored
View File

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

99
Cargo.lock generated
View File

@ -3,12 +3,12 @@
version = 3
[[package]]
name = "ahash"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
name = "aiken"
version = "0.0.1"
dependencies = [
"const-random",
"anyhow",
"clap",
"uplc",
]
[[package]]
@ -40,21 +40,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "clap"
version = "3.1.18"
@ -94,53 +79,14 @@ dependencies = [
"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]]
name = "flat"
version = "0.0.0"
version = "0.0.1"
dependencies = [
"anyhow",
"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]]
name = "hashbrown"
version = "0.11.2"
@ -190,15 +136,6 @@ version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "neptune"
version = "0.0.0"
dependencies = [
"anyhow",
"clap",
"uplc",
]
[[package]]
name = "os_str_bytes"
version = "6.0.1"
@ -256,12 +193,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.39"
@ -357,15 +288,6 @@ dependencies = [
"syn",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "unicode-ident"
version = "1.0.0"
@ -374,9 +296,8 @@ checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
[[package]]
name = "uplc"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"chumsky",
"flat",
"hex",
"peg",
@ -391,12 +312,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "winapi"
version = "0.3.9"

View File

@ -1,22 +1,23 @@
# Neptune
# AIKEN
Experiments with Plutus Core
A cardano smart contract language and toolchain
## Roadmap
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
- [ ] reverse the on chain encoding into plutus core
- [x] compile plutus core into it's on chain encoding
- [x] reverse the on chain encoding into plutus core
- [ ] Plutus Core interpreter
- [ ] create a higher level syntax with inspiration from
- JS
- ReasonML
- Elm
- Roc
- Rust
- Gleam
- [ ] Language Server
- [ ] Plutus Core interpreter
## Resources

View File

@ -1,6 +1,6 @@
[package]
name = "neptune"
version = "0.0.0"
name = "aiken"
version = "0.0.1"
edition = "2021"
# 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)]
pub enum UplcCommand {
Flat { input: PathBuf },
Unflat { input: PathBuf },
Flat {
input: PathBuf,
#[clap(short, long)]
print: bool,
},
Unflat {
input: PathBuf,
#[clap(short, long)]
print: bool,
},
}
impl Default for Cli {

View File

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

View File

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

View File

@ -1,12 +1,11 @@
[package]
name = "uplc"
version = "0.1.0"
version = "0.0.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chumsky = "0.8.0"
flat = { path = "../flat" }
hex = "0.4.3"
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 builtins;
mod debruijn;
mod flat;
pub mod parser;
#[cfg(test)]
mod test;

View File

@ -1,20 +1,31 @@
use std::{collections::HashMap, str::FromStr};
use chumsky::{
prelude::{end, filter, just, recursive, Simple},
text::{ident, int, keyword, TextParser},
Parser,
};
use std::str::FromStr;
use crate::{
ast::{Constant, Name, Program, Term, Unique},
ast::{Constant, Name, Program, Term},
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! {
grammar parser() for str {
grammar uplc() for str {
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)
= major:number() "." minor:number() "." patch:number() {
@ -43,7 +54,9 @@ peg::parser! {
}
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>
= n:name() { Term::Var(n) }
@ -77,7 +90,9 @@ peg::parser! {
= "integer" _+ i:number() { Constant::Integer(i as isize) }
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
= "string" _+ "\"" s:[^ '"']* "\"" { Constant::String(String::from_iter(s)) }
@ -94,195 +109,19 @@ peg::parser! {
rule name() -> Name
= 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']
}
}
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)]
mod test {
use crate::ast::{Constant, Name, Program, Term};
#[test]
fn parse_program() {
let code = r#"
@ -290,12 +129,14 @@ mod test {
(con integer 11)
)
"#;
let result = super::program(code);
let program = super::program(code).unwrap();
assert!(result.is_ok());
let program = result.unwrap();
assert_eq!(program.version, (11, 22, 33));
assert_eq!(
program,
Program::<Name> {
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