summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-06-26 18:07:05 +0200
committerLaurenz <laurmaedje@gmail.com>2021-06-26 18:07:05 +0200
commit422b8e640f00977177a5a7250a3c56009eed10c4 (patch)
treeb1a21718e9511d776a6de47b713e3308bb1baaad /src
parentd53c933e4d56e6c0484d81814779ddb1597ee032 (diff)
With expressions
Diffstat (limited to 'src')
-rw-r--r--src/eval/mod.rs46
-rw-r--r--src/eval/value.rs20
-rw-r--r--src/parse/mod.rs70
-rw-r--r--src/parse/tokens.rs8
-rw-r--r--src/pretty.rs12
-rw-r--r--src/syntax/expr.rs18
-rw-r--r--src/syntax/token.rs3
-rw-r--r--src/syntax/visit.rs6
8 files changed, 135 insertions, 48 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 116d3c12..a9fff56b 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::<FuncValue>(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 e8ecffa2..28c7d588 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<FuncArg>,
}
@@ -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<Spanned<String>>,
+ pub name: Option<String>,
/// The value of the argument.
pub value: Spanned<Value>,
}
-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<dyn Bounds>);
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index d8000d8c..381d44e2 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<Expr> {
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<Expr> {
let start = p.next_start();
@@ -552,32 +569,37 @@ fn let_expr(p: &mut Parser) -> Option<Expr> {
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 a496010e..abc3d6a6 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<Token<'static>> {
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 e2942f6f..216a6370 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 24850d65..62f02399 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<Expr>,
/// The arguments to the function.
pub args: CallArgs,
@@ -435,6 +438,19 @@ pub struct ClosureExpr {
pub body: Rc<Expr>,
}
+/// 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<Expr>,
+ /// 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 254a56a2..3f07bb33 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 a1a848ef..cf819ef0 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 {