use chumsky::{input::SpannedInput, pratt::*, prelude::*}; use crate::{abstract_tree::*, error::Error, lexer::Token}; type ParserInput<'tokens, 'src> = SpannedInput, SimpleSpan, &'tokens [(Token<'src>, SimpleSpan)]>; fn parser<'tokens, 'src: 'tokens>() -> impl Parser< 'tokens, ParserInput<'tokens, 'src>, Vec<(Statement, SimpleSpan)>, extra::Err, SimpleSpan>>, > { recursive(|statement| { let identifier = select! { Token::Identifier(text) => Identifier::new(text), }; let identifier_statement = identifier.map(|identifier| Statement::Identifier(identifier)); let basic_value = select! { Token::None => Value::none(), Token::Boolean(boolean) => Value::boolean(boolean), Token::Integer(integer) => Value::integer(integer), Token::Float(float) => Value::float(float), Token::String(string) => Value::string(string.to_string()), }; let list = statement .clone() .separated_by(just(Token::Control(','))) .allow_trailing() .collect() .delimited_by(just(Token::Control('[')), just(Token::Control(']'))) .map(Value::list); let value = choice(( basic_value.map(|value| Statement::Value(value)), list.map(|list| Statement::Value(list)), )); let assignment = identifier .then_ignore(just(Token::Operator("="))) .then(statement.clone()) .map(|(identifier, statement)| { Statement::Assignment(Assignment::new(identifier, statement)) }); let atom = choice(( identifier_statement, value.clone(), assignment.clone(), statement .clone() .delimited_by(just(Token::Control('(')), just(Token::Control(')'))), )); let logic = atom.pratt(( prefix(2, just(Token::Operator("!")), |statement| { Statement::Logic(Box::new(Logic::Not(statement))) }), infix(left(1), just(Token::Operator("==")), |left, right| { Statement::Logic(Box::new(Logic::Equal(left, right))) }), infix(left(1), just(Token::Operator("!=")), |left, right| { Statement::Logic(Box::new(Logic::NotEqual(left, right))) }), infix(left(1), just(Token::Operator(">")), |left, right| { Statement::Logic(Box::new(Logic::Greater(left, right))) }), infix(left(1), just(Token::Operator("<")), |left, right| { Statement::Logic(Box::new(Logic::Less(left, right))) }), infix(left(1), just(Token::Operator(">=")), |left, right| { Statement::Logic(Box::new(Logic::GreaterOrEqual(left, right))) }), infix(left(1), just(Token::Operator("<=")), |left, right| { Statement::Logic(Box::new(Logic::LessOrEqual(left, right))) }), infix(left(1), just(Token::Operator("&&")), |left, right| { Statement::Logic(Box::new(Logic::And(left, right))) }), infix(left(1), just(Token::Operator("||")), |left, right| { Statement::Logic(Box::new(Logic::Or(left, right))) }), )); choice((assignment, logic, value, identifier_statement)) }) .map_with(|statement, state| (statement, state.span())) .repeated() .collect() } pub fn parse<'tokens>( tokens: &'tokens [(Token, SimpleSpan)], ) -> Result, Error<'tokens>> { parser() .parse(tokens.spanned((0..0).into())) .into_result() .map_err(|error| Error::Parse(error)) } #[cfg(test)] mod tests { use crate::{ abstract_tree::{value::ValueInner, Logic}, lexer::lex, }; use super::*; #[test] fn identifier() { assert_eq!( parse(&lex("x").unwrap()).unwrap()[0].0, Statement::Identifier(Identifier::new("x")), ); assert_eq!( parse(&lex("foobar").unwrap()).unwrap()[0].0, Statement::Identifier(Identifier::new("foobar")), ); assert_eq!( parse(&lex("HELLO").unwrap()).unwrap()[0].0, Statement::Identifier(Identifier::new("HELLO")), ); } #[test] fn assignment() { assert_eq!( parse(&lex("foobar = 1").unwrap()).unwrap()[0].0, Statement::Assignment(Assignment::new( Identifier::new("foobar"), Statement::Value(Value::integer(1)) )), ); } #[test] fn logic() { assert_eq!( parse(&lex("x == 1").unwrap()).unwrap()[0].0, Statement::Logic(Box::new(Logic::Equal( Statement::Identifier(Identifier::new("x")), Statement::Value(Value::integer(1)) ))), ); } #[test] fn list() { assert_eq!( parse(&lex("[]").unwrap()).unwrap()[0].0, Statement::Value(Value::list(vec![])), ); assert_eq!( parse(&lex("[42]").unwrap()).unwrap()[0].0, Statement::Value(Value::list(vec![Statement::Value(Value::integer(42))])), ); assert_eq!( parse(&lex("[42, 'foo', 'bar', [1, 2, 3,]]").unwrap()).unwrap()[0].0, Statement::Value(Value::list(vec![ Statement::Value(Value::integer(42)), Statement::Value(Value::string("foo")), Statement::Value(Value::string("bar")), Statement::Value(Value::list(vec![ Statement::Value(Value::integer(1)), Statement::Value(Value::integer(2)), Statement::Value(Value::integer(3)), ])) ])), ); } #[test] fn r#true() { assert_eq!( parse(&lex("true").unwrap()).unwrap()[0].0, Statement::Value(Value::boolean(true)) ); } #[test] fn r#false() { assert_eq!( parse(&lex("false").unwrap()).unwrap()[0].0, Statement::Value(Value::boolean(false)) ); } #[test] fn positive_float() { assert_eq!( parse(&lex("0.0").unwrap()).unwrap()[0].0, Statement::Value(Value::float(0.0)) ); assert_eq!( parse(&lex("42.0").unwrap()).unwrap()[0].0, Statement::Value(Value::float(42.0)) ); let max_float = f64::MAX.to_string() + ".0"; assert_eq!( parse(&lex(&max_float).unwrap()).unwrap()[0].0, Statement::Value(Value::float(f64::MAX)) ); let min_positive_float = f64::MIN_POSITIVE.to_string(); assert_eq!( parse(&lex(&min_positive_float).unwrap()).unwrap()[0].0, Statement::Value(Value::float(f64::MIN_POSITIVE)) ); } #[test] fn negative_float() { assert_eq!( parse(&lex("-0.0").unwrap()).unwrap()[0].0, Statement::Value(Value::float(-0.0)) ); assert_eq!( parse(&lex("-42.0").unwrap()).unwrap()[0].0, Statement::Value(Value::float(-42.0)) ); let min_float = f64::MIN.to_string() + ".0"; assert_eq!( parse(&lex(&min_float).unwrap()).unwrap()[0].0, Statement::Value(Value::float(f64::MIN)) ); let max_negative_float = format!("-{}", f64::MIN_POSITIVE); assert_eq!( parse(&lex(&max_negative_float).unwrap()).unwrap()[0].0, Statement::Value(Value::float(-f64::MIN_POSITIVE)) ); } #[test] fn other_float() { assert_eq!( parse(&lex("Infinity").unwrap()).unwrap()[0].0, Statement::Value(Value::float(f64::INFINITY)) ); assert_eq!( parse(&lex("-Infinity").unwrap()).unwrap()[0].0, Statement::Value(Value::float(f64::NEG_INFINITY)) ); if let Statement::Value(value) = &parse(&lex("NaN").unwrap()).unwrap()[0].0 { if let ValueInner::Float(float) = value.inner().as_ref() { return assert!(float.is_nan()); } } panic!("Expected a float.") } #[test] fn positive_integer() { for i in 0..10 { let source = i.to_string(); let statements = parse(&lex(&source).unwrap()).unwrap(); assert_eq!(statements[0].0, Statement::Value(Value::integer(i))) } assert_eq!( parse(&lex("42").unwrap()).unwrap()[0].0, Statement::Value(Value::integer(42)) ); let maximum_integer = i64::MAX.to_string(); assert_eq!( parse(&lex(&maximum_integer).unwrap()).unwrap()[0].0, Statement::Value(Value::integer(i64::MAX)) ); } #[test] fn negative_integer() { for i in -9..1 { let source = i.to_string(); let statements = parse(&lex(&source).unwrap()).unwrap(); assert_eq!(statements[0].0, Statement::Value(Value::integer(i))) } assert_eq!( parse(&lex("-42").unwrap()).unwrap()[0].0, Statement::Value(Value::integer(-42)) ); let minimum_integer = i64::MIN.to_string(); assert_eq!( parse(&lex(&minimum_integer).unwrap()).unwrap()[0].0, Statement::Value(Value::integer(i64::MIN)) ); } #[test] fn double_quoted_string() { assert_eq!( parse(&lex("\"\"").unwrap()).unwrap()[0].0, Statement::Value(Value::string("".to_string())) ); assert_eq!( parse(&lex("\"42\"").unwrap()).unwrap()[0].0, Statement::Value(Value::string("42".to_string())) ); assert_eq!( parse(&lex("\"foobar\"").unwrap()).unwrap()[0].0, Statement::Value(Value::string("foobar".to_string())) ); } #[test] fn single_quoted_string() { assert_eq!( parse(&lex("''").unwrap()).unwrap()[0].0, Statement::Value(Value::string("".to_string())) ); assert_eq!( parse(&lex("'42'").unwrap()).unwrap()[0].0, Statement::Value(Value::string("42".to_string())) ); assert_eq!( parse(&lex("'foobar'").unwrap()).unwrap()[0].0, Statement::Value(Value::string("foobar".to_string())) ); } #[test] fn grave_quoted_string() { assert_eq!( parse(&lex("``").unwrap()).unwrap()[0].0, Statement::Value(Value::string("".to_string())) ); assert_eq!( parse(&lex("`42`").unwrap()).unwrap()[0].0, Statement::Value(Value::string("42".to_string())) ); assert_eq!( parse(&lex("`foobar`").unwrap()).unwrap()[0].0, Statement::Value(Value::string("foobar".to_string())) ); } }