use std::{rc::Rc, str::FromStr}; use crate::{ ast::{Constant, Name, Program, Term}, builtins::DefaultFunction, }; use interner::Interner; use pallas_primitives::{alonzo::PlutusData, Fragment}; use peg::{error::ParseError, str::LineCol}; mod interner; /// Parse a `Program` from a str. pub fn program(src: &str) -> Result, ParseError> { // initialize the string interner to get unique name let mut interner = Interner::new(); // run the generated parser let mut program = uplc::program(src)?; // assign proper unique ids in place interner.program(&mut program); Ok(program) } pub fn term(src: &str) -> Result, ParseError> { // initialize the string interner to get unique name let mut interner = Interner::new(); // run the generated parser let mut term = uplc::term(src)?; // assign proper unique ids in place interner.term(&mut term); Ok(term) } peg::parser! { grammar uplc() for str { pub rule program() -> Program = _* "(" _* "program" _+ v:version() _+ t:term() _* ")" _* { Program {version: v, term: t} } rule version() -> (usize, usize, usize) = major:number() "." minor:number() "." patch:number() { (major as usize, minor as usize, patch as usize) } pub rule term() -> Term = constant() / builtin() / var() / lambda() / apply() / delay() / force() / error() rule constant() -> Term = "(" _* "con" _+ con:( constant_integer() / constant_bytestring() / constant_string() / constant_unit() / constant_bool() / constant_data() ) _* ")" { Term::Constant(con) } rule builtin() -> Term = "(" _* "builtin" _+ b:ident() _* ")" { Term::Builtin(DefaultFunction::from_str(&b).unwrap()) } rule var() -> Term = n:name() { Term::Var(n) } rule lambda() -> Term = "(" _* "lam" _+ parameter_name:name() _+ t:term() _* ")" { Term::Lambda { parameter_name, body: Rc::new(t) } } #[cache_left_rec] rule apply() -> Term = "[" _* initial:term() _+ terms:(t:term() _* { t })+ "]" { terms .into_iter() .fold(initial, |lhs, rhs| Term::Apply { function: Rc::new(lhs), argument: Rc::new(rhs) }) } rule delay() -> Term = "(" _* "delay" _* t:term() _* ")" { Term::Delay(Rc::new(t)) } rule force() -> Term = "(" _* "force" _* t:term() _* ")" { Term::Force(Rc::new(t)) } rule error() -> Term = "(" _* "error" _* ")" { Term::Error } rule constant_integer() -> Constant = "integer" _+ i:big_number() { Constant::Integer(i as i128) } rule constant_bytestring() -> Constant = "bytestring" _+ "#" i:ident()* { Constant::ByteString(hex::decode(String::from_iter(i)).unwrap()) } rule constant_string() -> Constant = "string" _+ "\"" s:[^ '"']* "\"" { Constant::String(String::from_iter(s)) } rule constant_bool() -> Constant = "bool" _+ b:$("True" / "False") { Constant::Bool(b == "True") } rule constant_unit() -> Constant = "unit" _+ "()" { Constant::Unit } rule number() -> isize = n:$("-"* ['0'..='9']+) {? n.parse().or(Err("isize")) } rule big_number() -> i128 = n:$("-"* ['0'..='9']+) {? n.parse().or(Err("i128")) } rule constant_data() -> Constant = "data" _+ "#" i:ident()* { Constant::Data( PlutusData::decode_fragment( hex::decode(String::from_iter(i)).unwrap().as_slice() ).unwrap() ) } 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 _ = [' ' | '\n'] } } #[cfg(test)] mod test { use crate::ast::{Constant, Name, Program, Term}; #[test] fn parse_program() { let code = r#" (program 11.22.33 (con integer 11) ) "#; let program = super::program(code).unwrap(); assert_eq!( program, Program:: { version: (11, 22, 33), term: Term::Constant(Constant::Integer(11)), } ); } }