feat: rename to aiken and add e2e tests for uplc
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
#![recursion_limit = "10000"]
|
||||
|
||||
pub mod ast;
|
||||
pub mod builtins;
|
||||
mod debruijn;
|
||||
mod flat;
|
||||
pub mod parser;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
57
crates/uplc/src/parser/interner.rs
Normal file
57
crates/uplc/src/parser/interner.rs
Normal 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(¶meter_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
41
crates/uplc/src/test.rs
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user