From dba82d544d8b13b06f89c026d399338907a5964c Mon Sep 17 00:00:00 2001 From: rvcas Date: Mon, 3 Oct 2022 17:00:58 -0400 Subject: [PATCH] feat: record update syntax --- crates/lang/src/ast.rs | 2 +- crates/lang/src/parser.rs | 62 ++++++++++++++++++++++++----- crates/lang/src/tests/parser.rs | 69 +++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 10 deletions(-) diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index 4a0d8f65..62350e88 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -447,7 +447,7 @@ pub struct TypedRecordUpdateArg { #[derive(Debug, Clone, PartialEq)] pub struct UntypedRecordUpdateArg { pub label: String, - // pub location: SrcSpan, + pub location: Span, pub value: UntypedExpr, } diff --git a/crates/lang/src/parser.rs b/crates/lang/src/parser.rs index bbbf04eb..95d20514 100644 --- a/crates/lang/src/parser.rs +++ b/crates/lang/src/parser.rs @@ -467,9 +467,12 @@ pub fn expr_parser( } enum Chain { - FieldAccess(String, Span), - RecordUpdate, Call(Vec, Span), + FieldAccess(String, Span), + RecordUpdate( + Box<(expr::UntypedExpr, Vec)>, + Span, + ), } let field_access_parser = just(Token::Dot) @@ -479,6 +482,29 @@ pub fn expr_parser( }) .map_with_span(Chain::FieldAccess); + let record_update_parser = just(Token::DotDot) + .ignore_then(r.clone()) + .then( + just(Token::Comma) + .ignore_then( + select! { Token::Name {name} => name } + .then_ignore(just(Token::Colon)) + .then(r.clone()) + .map_with_span(|(label, value), span| ast::UntypedRecordUpdateArg { + label, + value, + location: span, + }) + .separated_by(just(Token::Comma)) + .allow_trailing(), + ) + .or_not(), + ) + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) + .map_with_span(|(spread, args_opt), span| { + Chain::RecordUpdate(Box::new((spread, args_opt.unwrap_or_default())), span) + }); + let call_parser = choice(( select! { Token::Name { name } => name } .then_ignore(just(Token::Colon)) @@ -504,16 +530,11 @@ pub fn expr_parser( .delimited_by(just(Token::LeftParen), just(Token::RightParen)) .map_with_span(Chain::Call); - let chain = choice((field_access_parser, call_parser)); + let chain = choice((field_access_parser, record_update_parser, call_parser)); let chained = expr_unit_parser .then(chain.repeated()) .foldl(|e, chain| match chain { - Chain::FieldAccess(label, span) => expr::UntypedExpr::FieldAccess { - location: e.location().union(span), - label, - container: Box::new(e), - }, Chain::Call(args, span) => { let mut holes = Vec::new(); @@ -563,7 +584,30 @@ pub fn expr_parser( } } } - _ => todo!(), + + Chain::FieldAccess(label, span) => expr::UntypedExpr::FieldAccess { + location: e.location().union(span), + label, + container: Box::new(e), + }, + + Chain::RecordUpdate(data, span) => { + let (spread, arguments) = *data; + + let location = span.union(spread.location()); + + let spread = ast::RecordUpdateSpread { + base: Box::new(spread), + location, + }; + + expr::UntypedExpr::RecordUpdate { + location: e.location().union(span), + constructor: Box::new(e), + spread, + arguments, + } + } }); // Negate diff --git a/crates/lang/src/tests/parser.rs b/crates/lang/src/tests/parser.rs index 7e3335a5..bb2c6922 100644 --- a/crates/lang/src/tests/parser.rs +++ b/crates/lang/src/tests/parser.rs @@ -89,6 +89,10 @@ fn module() { map_add_x([ 1, 2, 3 ]) } + + fn update_name(user: User, name: String) -> User { + User { ..user, name: "Aiken", } + } "#; let len = code.chars().count(); @@ -923,6 +927,71 @@ fn module() { return_annotation: None, return_type: (), }, + ast::UntypedDefinition::Fn { + arguments: vec![ + ast::Arg { + arg_name: ast::ArgName::Named { + name: "user".to_string(), + location: Span::new(SrcId::empty(), 1685..1689), + }, + location: Span::new(SrcId::empty(), 1685..1695), + annotation: Some(ast::Annotation::Constructor { + location: Span::new(SrcId::empty(), 1691..1695), + module: None, + name: "User".to_string(), + arguments: vec![], + },), + tipo: (), + }, + ast::Arg { + arg_name: ast::ArgName::Named { + name: "name".to_string(), + location: Span::new(SrcId::empty(), 1697..1701), + }, + location: Span::new(SrcId::empty(), 1697..1709), + annotation: Some(ast::Annotation::Constructor { + location: Span::new(SrcId::empty(), 1703..1709), + module: None, + name: "String".to_string(), + arguments: vec![], + },), + tipo: (), + }, + ], + body: expr::UntypedExpr::RecordUpdate { + location: Span::new(SrcId::empty(), 1737..1768), + constructor: Box::new(expr::UntypedExpr::Var { + location: Span::new(SrcId::empty(), 1737..1741), + name: "User".to_string(), + }), + spread: ast::RecordUpdateSpread { + base: Box::new(expr::UntypedExpr::Var { + location: Span::new(SrcId::empty(), 1746..1750), + name: "user".to_string(), + }), + location: Span::new(SrcId::empty(), 1742..1768), + }, + arguments: vec![ast::UntypedRecordUpdateArg { + label: "name".to_string(), + location: Span::new(SrcId::empty(), 1752..1765), + value: expr::UntypedExpr::String { + location: Span::new(SrcId::empty(), 1758..1765), + value: "Aiken".to_string(), + }, + },], + }, + doc: None, + location: Span::new(SrcId::empty(), 1670..1782), + name: "update_name".to_string(), + public: false, + return_annotation: Some(ast::Annotation::Constructor { + location: Span::new(SrcId::empty(), 1714..1718), + module: None, + name: "User".to_string(), + arguments: vec![], + },), + return_type: (), + }, ] }, );