use std::{collections::HashMap, str::FromStr}; use combine::{ attempt, between, choice, many1, parser::{ char::{alpha_num, digit, hex_digit, space, spaces, string}, combinator::no_partial, }, skip_many1, stream::{position, state}, token, ParseError, Parser, Stream, }; use crate::{ ast::{Constant, Name, Program, Term, Unique}, builtins::DefaultFunction, }; struct ParserState { identifiers: HashMap, current: Unique, } type StateStream = state::Stream; 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) -> anyhow::Result> { let mut parser = program_(); let result = parser.parse(state::Stream { stream: position::Stream::new(src.trim()), state: ParserState::new(), }); match result { Ok((program, _)) => Ok(program), Err(err) => Err(anyhow::anyhow!("{}", err)), } } fn program_() -> impl Parser, Output = Program> where Input: Stream, Input::Error: ParseError, { let prog = string("program").with(skip_many1(space())).with( (version(), skip_many1(space()), term().skip(spaces())) .map(|(version, _, term)| Program { version, term }), ); between(token('('), token(')'), prog).skip(spaces()) } fn version() -> impl Parser, Output = (usize, usize, usize)> where Input: Stream, Input::Error: ParseError, { ( many1(digit()), token('.'), many1(digit()), token('.'), many1(digit()), ) .map( |(major, _, minor, _, patch): (String, char, String, char, String)| { ( major.parse::().unwrap(), minor.parse::().unwrap(), patch.parse::().unwrap(), ) }, ) } fn term() -> impl Parser, Output = Term> where Input: Stream, Input::Error: ParseError, { opaque!(no_partial(choice(( attempt(var()), attempt(delay()), attempt(lambda()), attempt(apply()), attempt(constant()), attempt(force()), attempt(error()), attempt(builtin()), )))) } fn var() -> impl Parser, Output = Term> where Input: Stream, Input::Error: ParseError, { (many1(alpha_num()), spaces()).map_input( |(text, _): (String, _), input: &mut StateStream| { Term::Var(Name { unique: input.state.intern(&text), text, }) }, ) } fn delay() -> impl Parser, Output = Term> where Input: Stream, Input::Error: ParseError, { between( token('('), token(')'), string("delay") .with(skip_many1(space())) .with(term()) .map(|term| Term::Delay(Box::new(term))), ) } fn force() -> impl Parser, Output = Term> where Input: Stream, Input::Error: ParseError, { between( token('('), token(')'), string("force") .with(skip_many1(space())) .with(term()) .map(|term| Term::Force(Box::new(term))), ) } fn lambda() -> impl Parser, Output = Term> where Input: Stream, Input::Error: ParseError, { let name = many1(alpha_num()).map_input(|text: String, input: &mut StateStream| Name { unique: input.state.intern(&text), text, }); between( token('('), token(')'), string("lam") .with(skip_many1(space())) .with((name, skip_many1(space()), term())) .map(|(parameter_name, _, term)| Term::Lambda { parameter_name, body: Box::new(term), }), ) } fn apply() -> impl Parser, Output = Term> where Input: Stream, Input::Error: ParseError, { between( token('['), token(']'), (term().skip(skip_many1(space())), term()).map(|(function, argument)| Term::Apply { function: Box::new(function), argument: Box::new(argument), }), ) } fn builtin() -> impl Parser, Output = Term> where Input: Stream, Input::Error: ParseError, { between( token('('), token(')'), string("builtin") .with(skip_many1(space())) .with(many1(alpha_num())) .map(|builtin_name: String| { Term::Builtin(DefaultFunction::from_str(&builtin_name).unwrap()) }), ) } fn error() -> impl Parser, Output = Term> where Input: Stream, Input::Error: ParseError, { between( token('('), token(')'), string("error") .with(skip_many1(space())) .map(|_| Term::Error), ) } fn constant() -> impl Parser, Output = Term> where Input: Stream, Input::Error: ParseError, { between( token('('), token(')'), string("con") .with(skip_many1(space())) .with(choice(( attempt(constant_integer()), attempt(constant_bytestring()), attempt(constant_string()), attempt(constant_unit()), attempt(constant_bool()), ))) .map(Term::Constant), ) } fn constant_integer() -> impl Parser, Output = Constant> where Input: Stream, Input::Error: ParseError, { string("integer") .with(skip_many1(space())) .with(many1(digit())) .map(|d: String| Constant::Integer(d.parse::().unwrap())) } fn constant_bytestring() -> impl Parser, Output = Constant> where Input: Stream, Input::Error: ParseError, { string("bytestring") .with(skip_many1(space())) .with(token('#')) .with(many1(hex_digit())) .map(|b: String| Constant::ByteString(hex::decode(b).unwrap())) } fn constant_string() -> impl Parser, Output = Constant> where Input: Stream, Input::Error: ParseError, { string("string") .with(skip_many1(space())) .with(between(token('"'), token('"'), many1(alpha_num()))) .map(Constant::String) } fn constant_unit() -> impl Parser, Output = Constant> where Input: Stream, Input::Error: ParseError, { string("unit") .with(skip_many1(space())) .with(string("()")) .map(|_| Constant::Unit) } fn constant_bool() -> impl Parser, Output = Constant> where Input: Stream, Input::Error: ParseError, { string("bool") .with(skip_many1(space())) .with(string("True").or(string("False"))) .map(|b| Constant::Bool(b == "True")) } #[cfg(test)] mod test { #[test] fn parse_program() { let code = r#" (program 11.22.33 (con integer 11) ) "#; let result = super::program(code); assert!(result.is_ok()); let program = result.unwrap(); assert_eq!(program.version, (11, 22, 33)); } }