use chumsky::prelude::*; use crate::{ ast, expr::UntypedExpr, parser::{ error::{self, ParseError}, token::Token, }, }; pub fn parser( r: Recursive<'_, Token, UntypedExpr, ParseError>, ) -> impl Parser + '_ { choice(( select! {Token::Name { name } => name} .map_with_span(|module, span: ast::Span| (module, span)) .then_ignore(just(Token::Dot)) .or_not() .then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span))) .then( choice(( select! {Token::Name {name} => name} .then_ignore(just(Token::Colon)) .then(choice(( r.clone(), select! {Token::DiscardName {name} => name }.validate( |_name, span, emit| { emit(ParseError::expected_input_found( span, None, Some(error::Pattern::Discard), )); UntypedExpr::Var { location: span, name: ast::CAPTURE_VARIABLE.to_string(), } }, ), ))) .map_with_span(|(label, value), span| ast::CallArg { location: span, value, label: Some(label), }), choice(( select! {Token::Name {name} => name}.map_with_span(|name, span| { ( UntypedExpr::Var { name: name.clone(), location: span, }, name, ) }), select! {Token::DiscardName {name} => name }.validate( |name, span, emit| { emit(ParseError::expected_input_found( span, None, Some(error::Pattern::Discard), )); ( UntypedExpr::Var { location: span, name: ast::CAPTURE_VARIABLE.to_string(), }, name, ) }, ), )) .map(|(value, name)| ast::CallArg { location: value.location(), value, label: Some(name), }), )) .separated_by(just(Token::Comma)) .allow_trailing() .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), ), select! {Token::Name { name } => name} .map_with_span(|module, span| (module, span)) .then_ignore(just(Token::Dot)) .or_not() .then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span))) .then( select! {Token::Name {name} => name} .ignored() .then_ignore(just(Token::Colon)) .validate(|_label, span, emit| { emit(ParseError::expected_input_found( span, None, Some(error::Pattern::Label), )) }) .or_not() .then(choice(( r.clone(), select! {Token::DiscardName {name} => name }.validate( |_name, span, emit| { emit(ParseError::expected_input_found( span, None, Some(error::Pattern::Discard), )); UntypedExpr::Var { location: span, name: ast::CAPTURE_VARIABLE.to_string(), } }, ), ))) .map(|(_label, value)| ast::CallArg { location: value.location(), value, label: None, }) .separated_by(just(Token::Comma)) .allow_trailing() .delimited_by(just(Token::LeftParen), just(Token::RightParen)), ), )) .map_with_span(|((module, (name, n_span)), arguments), span| { let fun = if let Some((module, m_span)) = module { UntypedExpr::FieldAccess { location: m_span.union(n_span), label: name, container: Box::new(UntypedExpr::Var { location: m_span, name: module, }), } } else { UntypedExpr::Var { location: n_span, name, } }; UntypedExpr::Call { arguments, fun: Box::new(fun), location: span, } }) } #[cfg(test)] mod tests { use crate::assert_expr; #[test] fn record_create_labeled() { assert_expr!(r#"User { name: "Aiken", age, thing: 2 }"#); } #[test] fn record_create_labeled_with_field_access() { assert_expr!(r#"some_module.User { name: "Aiken", age, thing: 2 }"#); } #[test] fn record_create_unlabeled() { assert_expr!(r#"some_module.Thing(1, a)"#); } }