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
17 changed files with 9915 additions and 330 deletions

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);
}