use crate::{ ast::{well_known, TraceKind}, expr::UntypedExpr, parser::{ error::{ParseError, Pattern}, expr::{string, when::clause}, token::Token, }, }; use chumsky::prelude::*; pub fn parser<'a>( expression: Recursive<'a, Token, UntypedExpr, ParseError>, sequence: Recursive<'a, Token, UntypedExpr, ParseError>, ) -> impl Parser + 'a { let message = choice(( clause(expression.clone()).ignored().rewind().to(None), choice((string::hybrid(), expression.clone())).or_not(), )) .boxed(); choice(( just(Token::Todo) .ignore_then(message.clone()) .map_with_span(UntypedExpr::todo), just(Token::Fail) .ignore_then(message) .map_with_span(UntypedExpr::fail), just(Token::Trace) .ignore_then(choice((string::hybrid(), expression.clone()))) .then( choice((just(Token::Colon), just(Token::Comma))) .then( choice((string::hybrid(), expression.clone())) .separated_by(just(Token::Comma)) .at_least(1), ) .validate(|(token, arguments), span, emit| { if token != Token::Colon { emit(ParseError::expected_but_got( Pattern::Token(Token::Colon), Pattern::Token(token), span.map(|start, _end| (start, start + 1)), )) } arguments }) .or_not() .map(|opt| opt.unwrap_or_default()), ) .then(sequence.clone().or_not()) .map_with_span( |((label, arguments), continuation), span| UntypedExpr::Trace { kind: TraceKind::Trace, location: span, then: Box::new(continuation.unwrap_or_else(|| UntypedExpr::Var { location: span, name: well_known::VOID.to_string(), })), label: Box::new(label), arguments, }, ), )) } #[cfg(test)] mod tests { use crate::assert_expr; #[test] fn error_basic() { assert_expr!( r#" fail @"foo" "# ); } #[test] fn error_sugar() { assert_expr!( r#" fail "foo" "# ); } #[test] fn todo_basic() { assert_expr!( r#" todo @"foo" "# ); } #[test] fn todo_sugar() { assert_expr!( r#" todo "foo" "# ); } #[test] fn todo_empty() { assert_expr!( r#" todo "# ); } #[test] fn todo_expr() { assert_expr!( r#" todo string.join(["foo", "bar"]) "# ); } #[test] fn fail_expr() { assert_expr!( r#" fail str.join([@"Some string ", some_params, @" some string"], @"") "# ); } #[test] fn fail_empty() { assert_expr!( r#" fail "# ); } #[test] fn trace_string() { assert_expr!( r#" trace @"foo" a "# ); } #[test] fn trace_bytearray() { assert_expr!( r#" trace "foo" a "# ); } #[test] fn trace_expr() { assert_expr!( r#" trace string.join(["foo", "bar"]) a "# ); } #[test] fn trace_expr_todo() { assert_expr!( r#" trace some_var "# ); } #[test] fn trace_labelled() { assert_expr!( r#" trace foo: "bar" "# ); } #[test] fn trace_variadic() { assert_expr!( r#" trace "foo": @"bar", baz "# ); } #[test] fn trace_dangling_colons() { assert_expr!( r#" let debug = fn() { trace "foo": } "# ); } }