diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-03-03 17:53:40 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-03-03 17:53:40 +0100 |
| commit | c94a18833f23d2b57de1b87971458fd54b56d088 (patch) | |
| tree | 9e1ed55cfca15aef6d39ced50a3a5b14d2800aae /src/parse | |
| parent | 4d90a066f197264341eff6bf67e8c06cae434eb4 (diff) | |
Closures and function definitions 🚀
Supports:
- Closure syntax: `(x, y) => z`
- Shorthand for a single argument: `x => y`
- Function syntax: `let f(x) = y`
- Capturing of variables from the environment
- Error messages for too few / many passed arguments
Does not support:
- Named arguments
- Variadic arguments with `..`
Diffstat (limited to 'src/parse')
| -rw-r--r-- | src/parse/mod.rs | 92 |
1 files changed, 78 insertions, 14 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 327a99f3..29801527 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -173,21 +173,37 @@ fn primary(p: &mut Parser) -> Option<Expr> { } match p.peek() { - // Function or identifier. + // Things that start with an identifier. Some(Token::Ident(string)) => { let ident = Ident { span: p.eat_span(), string: string.into(), }; - match p.peek_direct() { - Some(Token::LeftParen) | Some(Token::LeftBracket) => Some(call(p, ident)), - _ => Some(Expr::Ident(ident)), + // Parenthesis or bracket means this is a function call. + if matches!( + p.peek_direct(), + Some(Token::LeftParen) | Some(Token::LeftBracket), + ) { + return Some(call(p, ident)); } + + // Arrow means this is closure's lone parameter. + if p.eat_if(Token::Arrow) { + return expr(p).map(|body| { + Expr::Closure(ExprClosure { + span: ident.span.join(body.span()), + params: Rc::new(vec![ident]), + body: Rc::new(body), + }) + }); + } + + Some(Expr::Ident(ident)) } // Structures. - Some(Token::LeftParen) => Some(parenthesized(p)), + Some(Token::LeftParen) => parenthesized(p), Some(Token::LeftBracket) => Some(template(p)), Some(Token::LeftBrace) => Some(block(p, true)), @@ -228,23 +244,36 @@ fn literal(p: &mut Parser) -> Option<Expr> { Some(Expr::Lit(Lit { span: p.eat_span(), kind })) } -/// Parse a parenthesized expression, which can be either of: +/// Parse something that starts with a parenthesis, which can be either of: /// - Array literal /// - Dictionary literal /// - Parenthesized expression -pub fn parenthesized(p: &mut Parser) -> Expr { +/// - Parameter list of closure expression +pub fn parenthesized(p: &mut Parser) -> Option<Expr> { p.start_group(Group::Paren, TokenMode::Code); let colon = p.eat_if(Token::Colon); let (items, has_comma) = collection(p); let span = p.end_group(); + // Leading colon makes this a dictionary. if colon { - // Leading colon makes this a dictionary. - return dict(p, items, span); + return Some(dict(p, items, span)); + } + + // Arrow means this is closure's parameter list. + if p.eat_if(Token::Arrow) { + let params = params(p, items); + return expr(p).map(|body| { + Expr::Closure(ExprClosure { + span: span.join(body.span()), + params: Rc::new(params), + body: Rc::new(body), + }) + }); } // Find out which kind of collection this is. - match items.as_slice() { + Some(match items.as_slice() { [] => array(p, items, span), [ExprArg::Pos(_)] if !has_comma => match items.into_iter().next() { Some(ExprArg::Pos(expr)) => { @@ -254,7 +283,7 @@ pub fn parenthesized(p: &mut Parser) -> Expr { }, [ExprArg::Pos(_), ..] => array(p, items, span), [ExprArg::Named(_), ..] => dict(p, items, span), - } + }) } /// Parse a collection. @@ -331,6 +360,19 @@ fn dict(p: &mut Parser, items: Vec<ExprArg>, span: Span) -> Expr { Expr::Dict(ExprDict { span, items: items.collect() }) } +/// Convert a collection into a parameter list, producing errors for anything +/// other than identifiers. +fn params(p: &mut Parser, items: Vec<ExprArg>) -> Vec<Ident> { + let items = items.into_iter().filter_map(|item| match item { + ExprArg::Pos(Expr::Ident(id)) => Some(id), + _ => { + p.diag(error!(item.span(), "expected identifier")); + None + } + }); + items.collect() +} + // Parse a template value: `[...]`. fn template(p: &mut Parser) -> Expr { p.start_group(Group::Bracket, TokenMode::Markup); @@ -340,7 +382,7 @@ fn template(p: &mut Parser) -> Expr { } /// Parse a block expression: `{...}`. -fn block(p: &mut Parser, scopes: bool) -> Expr { +fn block(p: &mut Parser, scoping: bool) -> Expr { p.start_group(Group::Brace, TokenMode::Code); let mut exprs = vec![]; while !p.eof() { @@ -355,7 +397,7 @@ fn block(p: &mut Parser, scopes: bool) -> Expr { p.skip_white(); } let span = p.end_group(); - Expr::Block(ExprBlock { span, exprs, scoping: scopes }) + Expr::Block(ExprBlock { span, exprs, scoping }) } /// Parse an expression. @@ -445,16 +487,38 @@ fn expr_let(p: &mut Parser) -> Option<Expr> { let mut expr_let = None; if let Some(binding) = ident(p) { + // If a parenthesis follows, this is a function definition. + let mut parameters = None; + if p.peek_direct() == Some(Token::LeftParen) { + p.start_group(Group::Paren, TokenMode::Code); + let items = collection(p).0; + parameters = Some(params(p, items)); + p.end_group(); + } + let mut init = None; if p.eat_if(Token::Eq) { init = expr(p); + } else if parameters.is_some() { + // Function definitions must have a body. + p.expected_at("body", p.end()); + } + + // Rewrite into a closure expression if it's a function definition. + if let Some(params) = parameters { + let body = init?; + init = Some(Expr::Closure(ExprClosure { + span: binding.span.join(body.span()), + params: Rc::new(params), + body: Rc::new(body), + })); } expr_let = Some(Expr::Let(ExprLet { span: p.span(start), binding, init: init.map(Box::new), - })) + })); } expr_let |
