From 422b8e640f00977177a5a7250a3c56009eed10c4 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 26 Jun 2021 18:07:05 +0200 Subject: [PATCH] With expressions --- src/eval/mod.rs | 46 ++++++++++++++++++++++++--- src/eval/value.rs | 20 ++++-------- src/parse/mod.rs | 70 +++++++++++++++++++++++++++-------------- src/parse/tokens.rs | 8 +++-- src/pretty.rs | 12 ++++++- src/syntax/expr.rs | 18 ++++++++++- src/syntax/token.rs | 3 ++ src/syntax/visit.rs | 6 ++++ tests/ref/code/ops.png | Bin 782 -> 1404 bytes tests/typ/code/ops.typ | 18 +++++++++++ 10 files changed, 153 insertions(+), 48 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 116d3c122..a9fff56be 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -263,7 +263,8 @@ impl Eval for Expr { Self::Group(ref v) => v.eval(ctx), Self::Block(ref v) => v.eval(ctx), Self::Call(ref v) => v.eval(ctx), - Self::Closure(ref v) => Value::Func(v.eval(ctx)), + Self::Closure(ref v) => v.eval(ctx), + Self::With(ref v) => v.eval(ctx), Self::Unary(ref v) => v.eval(ctx), Self::Binary(ref v) => v.eval(ctx), Self::Let(ref v) => v.eval(ctx), @@ -498,11 +499,13 @@ impl Eval for CallArg { fn eval(&self, ctx: &mut EvalContext) -> Self::Output { match self { Self::Pos(expr) => FuncArg { + span: self.span(), name: None, value: Spanned::new(expr.eval(ctx), expr.span()), }, Self::Named(Named { name, expr }) => FuncArg { - name: Some(Spanned::new(name.string.clone(), name.span)), + span: self.span(), + name: Some(name.string.clone()), value: Spanned::new(expr.eval(ctx), expr.span()), }, } @@ -510,7 +513,7 @@ impl Eval for CallArg { } impl Eval for ClosureExpr { - type Output = FuncValue; + type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { let params = Rc::clone(&self.params); @@ -524,7 +527,7 @@ impl Eval for ClosureExpr { }; let name = self.name.as_ref().map(|id| id.to_string()); - FuncValue::new(name, move |ctx, args| { + Value::Func(FuncValue::new(name, move |ctx, args| { // Don't leak the scopes from the call site. Instead, we use the // scope of captured variables we collected earlier. let prev = mem::take(&mut ctx.scopes); @@ -539,7 +542,40 @@ impl Eval for ClosureExpr { let value = body.eval(ctx); ctx.scopes = prev; value - }) + })) + } +} + +impl Eval for WithExpr { + type Output = Value; + + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let callee = self.callee.eval(ctx); + if let Some(func) = ctx.cast::(callee, self.callee.span()) { + let applied = self.args.eval(ctx); + let name = func.name().map(|s| s.to_string()); + Value::Func(FuncValue::new(name, move |ctx, args| { + // Remove named arguments that were overridden. + let kept: Vec<_> = applied + .items + .iter() + .filter(|arg| { + arg.name.is_none() + || args.items.iter().all(|other| arg.name != other.name) + }) + .cloned() + .collect(); + + // Preprend the applied arguments so that the positional arguments + // are in the right order. + args.items.splice(.. 0, kept); + + // Call the original function. + func(ctx, args) + })) + } else { + Value::Error + } } } diff --git a/src/eval/value.rs b/src/eval/value.rs index e8ecffa2e..28c7d5889 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -274,7 +274,7 @@ impl Debug for FuncValue { pub struct FuncArgs { /// The span of the whole argument list. pub span: Span, - /// The arguments. + /// The positional arguments. pub items: Vec, } @@ -346,7 +346,7 @@ impl FuncArgs { let index = self .items .iter() - .position(|arg| arg.name.as_ref().map(|s| s.v.as_str()) == Some(name))?; + .position(|arg| arg.name.as_ref().map_or(false, |other| name == other))?; let value = self.items.remove(index).value; let span = value.span; @@ -373,7 +373,7 @@ impl FuncArgs { pub fn finish(self, ctx: &mut EvalContext) { for arg in &self.items { if arg.value.v != Value::Error { - ctx.diag(error!(arg.span(), "unexpected argument")); + ctx.diag(error!(arg.span, "unexpected argument")); } } } @@ -382,22 +382,14 @@ impl FuncArgs { /// An argument to a function call: `12` or `draw: false`. #[derive(Debug, Clone, PartialEq)] pub struct FuncArg { + /// The span of the whole argument. + pub span: Span, /// The name of the argument (`None` for positional arguments). - pub name: Option>, + pub name: Option, /// The value of the argument. pub value: Spanned, } -impl FuncArg { - /// The source code location. - pub fn span(&self) -> Span { - match &self.name { - Some(name) => name.span.join(self.value.span), - None => self.value.span, - } - } -} - /// A wrapper around a dynamic value. pub struct AnyValue(Box); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index d8000d8cc..381d44e2f 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -245,6 +245,10 @@ fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> Option { continue; } + if p.eat_if(Token::With) { + lhs = with_expr(p, lhs); + } + if atomic { break; } @@ -545,6 +549,19 @@ fn args(p: &mut Parser) -> CallArgs { CallArgs { span: p.span(start), items } } +/// Parse a with expression. +fn with_expr(p: &mut Parser, callee: Expr) -> Expr { + p.start_group(Group::Paren, TokenMode::Code); + let args = args(p); + p.end_group(); + + Expr::With(WithExpr { + span: p.span(callee.span().start), + callee: Box::new(callee), + args, + }) +} + /// Parse a let expression. fn let_expr(p: &mut Parser) -> Option { let start = p.next_start(); @@ -552,32 +569,37 @@ fn let_expr(p: &mut Parser) -> Option { let mut let_expr = None; if let Some(binding) = ident(p) { - // If a parenthesis follows, this is a function definition. - let mut params = None; - if p.peek_direct() == Some(Token::LeftParen) { - p.start_group(Group::Paren, TokenMode::Code); - let items = collection(p).0; - params = Some(idents(p, items)); - p.end_group(); - } - let mut init = None; - if p.eat_if(Token::Eq) { - init = expr(p); - } else if params.is_some() { - // Function definitions must have a body. - p.expected_at("body", p.prev_end()); - } - // Rewrite into a closure expression if it's a function definition. - if let Some(params) = params { - let body = init?; - init = Some(Expr::Closure(ClosureExpr { - span: binding.span.join(body.span()), - name: Some(binding.clone()), - params: Rc::new(params), - body: Rc::new(body), - })); + if p.eat_if(Token::With) { + init = Some(with_expr(p, Expr::Ident(binding.clone()))); + } else { + // If a parenthesis follows, this is a function definition. + let mut params = None; + if p.peek_direct() == Some(Token::LeftParen) { + p.start_group(Group::Paren, TokenMode::Code); + let items = collection(p).0; + params = Some(idents(p, items)); + p.end_group(); + } + + if p.eat_if(Token::Eq) { + init = expr(p); + } else if params.is_some() { + // Function definitions must have a body. + p.expected_at("body", p.prev_end()); + } + + // Rewrite into a closure expression if it's a function definition. + if let Some(params) = params { + let body = init?; + init = Some(Expr::Closure(ClosureExpr { + span: binding.span.join(body.span()), + name: Some(binding.clone()), + params: Rc::new(params), + body: Rc::new(body), + })); + } } let_expr = Some(Expr::Let(LetExpr { diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index a496010e3..abc3d6a6c 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -366,9 +366,6 @@ impl<'s> Tokens<'s> { fn ident(&mut self, start: usize) -> Token<'s> { self.s.eat_while(is_id_continue); match self.s.eaten_from(start) { - "not" => Token::Not, - "and" => Token::And, - "or" => Token::Or, "none" => Token::None, "auto" => Token::Auto, "true" => Token::Bool(true), @@ -489,6 +486,10 @@ impl Debug for Tokens<'_> { fn keyword(id: &str) -> Option> { Some(match id { + "not" => Token::Not, + "and" => Token::And, + "or" => Token::Or, + "with" => Token::With, "let" => Token::Let, "if" => Token::If, "else" => Token::Else, @@ -777,6 +778,7 @@ mod tests { fn test_tokenize_keywords() { // A list of a few (not all) keywords. let list = [ + ("not", Not), ("let", Let), ("if", If), ("else", Else), diff --git a/src/pretty.rs b/src/pretty.rs index e2942f6fd..216a63702 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -217,6 +217,7 @@ impl Pretty for Expr { Self::Binary(v) => v.pretty(p), Self::Call(v) => v.pretty(p), Self::Closure(v) => v.pretty(p), + Self::With(v) => v.pretty(p), Self::Let(v) => v.pretty(p), Self::If(v) => v.pretty(p), Self::While(v) => v.pretty(p), @@ -370,6 +371,15 @@ impl Pretty for ClosureExpr { } } +impl Pretty for WithExpr { + fn pretty(&self, p: &mut Printer) { + self.callee.pretty(p); + p.push_str(" with ("); + self.args.pretty(p); + p.push(')'); + } +} + impl Pretty for LetExpr { fn pretty(&self, p: &mut Printer) { p.push_str("let "); @@ -539,7 +549,7 @@ impl Pretty for FuncArgs { impl Pretty for FuncArg { fn pretty(&self, p: &mut Printer) { if let Some(name) = &self.name { - p.push_str(&name.v); + p.push_str(&name); p.push_str(": "); } self.value.v.pretty(p); diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 24850d651..62f023990 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -52,6 +52,8 @@ pub enum Expr { Call(CallExpr), /// A closure expression: `(x, y) => z`. Closure(ClosureExpr), + /// A with expression: `f with (x, y: 1)`. + With(WithExpr), /// A let expression: `let x = 1`. Let(LetExpr), /// An if-else expression: `if x { y } else { z }`. @@ -91,6 +93,7 @@ impl Expr { Self::Binary(ref v) => v.span, Self::Call(ref v) => v.span, Self::Closure(ref v) => v.span, + Self::With(ref v) => v.span, Self::Let(ref v) => v.span, Self::If(ref v) => v.span, Self::While(ref v) => v.span, @@ -383,7 +386,7 @@ pub enum Associativity { pub struct CallExpr { /// The source code location. pub span: Span, - /// The callee of the function. + /// The function to call. pub callee: Box, /// The arguments to the function. pub args: CallArgs, @@ -435,6 +438,19 @@ pub struct ClosureExpr { pub body: Rc, } +/// A with expression: `f with (x, y: 1)`. +/// +/// Applies arguments to a function. +#[derive(Debug, Clone, PartialEq)] +pub struct WithExpr { + /// The source code location. + pub span: Span, + /// The function to apply the arguments to. + pub callee: Box, + /// The arguments to apply to the function. + pub args: CallArgs, +} + /// A let expression: `let x = 1`. #[derive(Debug, Clone, PartialEq)] pub struct LetExpr { diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 254a56a24..3f07bb338 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -74,6 +74,8 @@ pub enum Token<'s> { And, /// The `or` operator. Or, + /// The `with` operator. + With, /// The none literal: `none`. None, /// The auto literal: `auto`. @@ -241,6 +243,7 @@ impl<'s> Token<'s> { Self::Not => "operator `not`", Self::And => "operator `and`", Self::Or => "operator `or`", + Self::With => "operator `with`", Self::None => "`none`", Self::Auto => "`auto`", Self::Let => "keyword `let`", diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index a1a848ef9..cf819ef06 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -99,6 +99,7 @@ visit! { Expr::Binary(e) => v.visit_binary(e), Expr::Call(e) => v.visit_call(e), Expr::Closure(e) => v.visit_closure(e), + Expr::With(e) => v.visit_with(e), Expr::Let(e) => v.visit_let(e), Expr::If(e) => v.visit_if(e), Expr::While(e) => v.visit_while(e), @@ -176,6 +177,11 @@ visit! { } } + fn visit_with(v, node: &WithExpr) { + v.visit_expr(&node.callee); + v.visit_args(&node.args); + } + fn visit_let(v, node: &LetExpr) { v.visit_binding(&node.binding); if let Some(init) = &node.init { diff --git a/tests/ref/code/ops.png b/tests/ref/code/ops.png index b566b5b75e36800c3e9899f0aa519e38a4b024da..4421edb432d0bd4d0d456c9a9ff3400cbff78e58 100644 GIT binary patch delta 1358 zcmZ{kdov(K= z_V6}&R0yNQ&5_fCMC5t3FcXXB6}z3zxqtROpFh8Ue$FRR{pg`!;aXZ&ona zPgL+Q*)5`fdSK>4UcJFHMG;~G77o)Vp|UrJpz;x&YLonFe(3FKl1VW#eYSCrAW)ty z&lr#ou}{x&y(21@wC2c~dv(D3?&jJz{_(GJx6?VUo%$NC11&;0Sd}FD7X_vxDFznY z+fR#kljy-(|%S-V=6cT)fulyB{0 z4$e(ydVH>gwq15tB5UHYyLdE#9n>gEZTNi*zhzAfxG0fPWEU#|G-vQ73&2Teu&!57 zQ(S;8G>f2^1EjxElsp+3Ev&DopWTex$f)S>4oh)ca-vtyq!XO$nT-}ZMtuG%AbM@>L#52rsra>|hf-}$1z_7FPJR7#3dF^Qw$8r{4&OR2L1XrFE&B0+hV-ksMN z;37-R0MZB!T{y5jt&|V1#L&U?#pgUv?@?cz0olYl!<$ zfb6W>uWu-js8!mGf2xnqLc|p;jODP^?4|rFy8pFqFEe(kt>wXj1B>)T?2r$(O~@O~KRjY%lTlfs zPWIGlnfgiwI;ewmOjVwpz3$FRgqLDB#r>8ZPS{){DTf6+8u(91BTo&%D7@9BeTU2_ zTT0Hb$+MVy$=7z3(Fez><9WuYlJw>1XeKg&nxO!gk1H3w7v(mDLZJzF*i zJRmHrE$#mmC11k>i%I3-rG^crUu%K2JSbLkW#7ZQk8g}5CM>Xx1ny{l@5gwcB5x|T zm65XbW7{Y1D2-+(l8u&W$)BfdLbz@d_+i{ zE4smUw3;DEHwaR2Nx35(tKSSv;7Ua(183#2_I>`i^ga_p@4fflSA-4T-@ifpfH2_e zPLkUkXMdK?FiFcIofsvJkX3-BEgjQgh9d11A#i(%=`iEG_kJ$-2*;G3X)mD>a_L7% z>T=r+7YGvctwXO|ci^LgNPGzd-$v{`LTDed)zt>K+j)a!^X)L9083jB4}@b$IS}%n zIwt%q0@HU9&h<(g3B@rCNj=?ZyGID05nyTvl7Et30^wX&zdhe0Bp4)}+nP|zqZGi^ z!(mqu(kFyc&qr-Ktm!EcruvbkQV|kxS|=w3*xSPBAxcA=D)qfLsT@b`1vY77mP8v% z;H3qoqP5oR=$;KhQn~1}*Gcc;Sr#vj>7-NhvOR1KMltZ|c<3g$xB<&%D4@br=NK$U6pBO?& zl1l3^xBj*eQeI-M^qkY-zZ#Pe0~V7Y0|X?5Uy5L{SS%Kc#bU91Cw~FM@d*#qxH~QY O0000