From 710f88ccb2bceb9851a8fb0b7f131343ee33dbd5 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 26 Jan 2021 23:30:03 +0100 Subject: [PATCH] =?UTF-8?q?For=20loop=20patterns=20=F0=9F=A6=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/eval/mod.rs | 62 ++++++++++++++++++++++++++++++++-------------- src/parse/mod.rs | 13 +++++++++- src/syntax/expr.rs | 26 +++++++++++++++++-- src/syntax/mod.rs | 1 + 4 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 27c66095a..e04879d28 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -275,7 +275,7 @@ impl Eval for Spanned<&ExprBinary> { impl Spanned<&ExprBinary> { /// Apply a basic binary operation. - fn apply(&self, ctx: &mut EvalContext, op: F) -> Value + fn apply(self, ctx: &mut EvalContext, op: F) -> Value where F: FnOnce(Value, Value) -> Value, { @@ -311,7 +311,7 @@ impl Spanned<&ExprBinary> { } /// Apply an assignment operation. - fn assign(&self, ctx: &mut EvalContext, op: F) -> Value + fn assign(self, ctx: &mut EvalContext, op: F) -> Value where F: FnOnce(Value, Value) -> Value, { @@ -379,30 +379,56 @@ impl Eval for Spanned<&ExprFor> { fn eval(self, ctx: &mut EvalContext) -> Self::Output { let iter = self.v.iter.eval(ctx); - if let Value::Array(array) = iter { - let mut output = match self.v.body.v { - Expr::Template(_) => Value::Template(vec![]), - _ => Value::None, - }; + let mut output = if let Expr::Template(_) = self.v.body.v { + Value::Template(vec![]) + } else { + Value::None + }; - for value in array { - ctx.scopes.define(self.v.pat.v.as_str(), value); - let value = self.v.body.eval(ctx); + macro_rules! iterate { + (for ($($binding:ident => $value:ident),*) in $iter:expr) => { + #[allow(unused_parens)] + for ($($value),*) in $iter { + $(ctx.scopes.define($binding.as_str(), $value);)* - if let Value::Template(prev) = &mut output { - if let Value::Template(new) = value { - prev.extend(new); + let value = self.v.body.eval(ctx); + + if let Value::Template(prev) = &mut output { + if let Value::Template(new) = value { + prev.extend(new); + } } } + + return output; + }; + } + + match (self.v.pat.v.clone(), iter) { + (ForPattern::Value(v), Value::Str(string)) => { + iterate!(for (v => value) in string.chars().map(|c| Value::Str(c.into()))); + } + (ForPattern::Value(v), Value::Array(array)) => { + iterate!(for (v => value) in array.into_iter()); + } + (ForPattern::Value(v), Value::Dict(dict)) => { + iterate!(for (v => value) in dict.into_iter().map(|p| p.1)); + } + (ForPattern::KeyValue(k, v), Value::Dict(dict)) => { + iterate!(for (k => key, v => value) in dict.into_iter()); } - return output; - } else if iter != Value::Error { - ctx.diag(error!( + (ForPattern::KeyValue(..), Value::Str(_)) + | (ForPattern::KeyValue(..), Value::Array(_)) => { + ctx.diag(error!(self.v.pat.span, "mismatched pattern",)); + } + + (_, Value::Error) => {} + (_, iter) => ctx.diag(error!( self.v.iter.span, - "expected array, found {}", + "cannot loop over {}", iter.type_name(), - )); + )), } Value::Error diff --git a/src/parse/mod.rs b/src/parse/mod.rs index ccf333b92..8687ac246 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -423,7 +423,7 @@ fn expr_for(p: &mut Parser) -> Option { p.assert(Token::For); let mut expr_for = None; - if let Some(pat) = p.span_if(ident) { + if let Some(pat) = p.span_if(for_pattern) { if p.expect(Token::In) { if let Some(iter) = p.span_if(expr) { if let Some(body) = p.span_if(body) { @@ -440,6 +440,17 @@ fn expr_for(p: &mut Parser) -> Option { expr_for } +/// Parse a for loop pattern. +fn for_pattern(p: &mut Parser) -> Option { + let first = ident(p)?; + if p.eat_if(Token::Comma) { + if let Some(second) = ident(p) { + return Some(ForPattern::KeyValue(first, second)); + } + } + Some(ForPattern::Value(first)) +} + /// Parse an identifier. fn ident(p: &mut Parser) -> Option { match p.peek() { diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index ea7625113..9f3bd9ad2 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -528,7 +528,7 @@ impl Pretty for ExprIf { #[derive(Debug, Clone, PartialEq)] pub struct ExprFor { /// The pattern to assign to. - pub pat: Spanned, + pub pat: Spanned, /// The expression to iterate over. pub iter: SpanBox, /// The expression to evaluate for each iteration. @@ -538,10 +538,32 @@ pub struct ExprFor { impl Pretty for ExprFor { fn pretty(&self, p: &mut Printer) { p.push_str("#for "); - p.push_str(&self.pat.v); + self.pat.v.pretty(p); p.push_str(" #in "); self.iter.v.pretty(p); p.push_str(" "); self.body.v.pretty(p); } } + +/// A pattern in a for loop. +#[derive(Debug, Clone, PartialEq)] +pub enum ForPattern { + /// A value pattern: `#for v #in array`. + Value(Ident), + /// A key-value pattern: `#for k, v #in dict`. + KeyValue(Ident, Ident), +} + +impl Pretty for ForPattern { + fn pretty(&self, p: &mut Printer) { + match self { + Self::Value(v) => p.push_str(&v), + Self::KeyValue(k, v) => { + p.push_str(&k); + p.push_str(", "); + p.push_str(&v); + } + } + } +} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index fa1308ded..0b2ac06f9 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -127,5 +127,6 @@ mod tests { roundtrip("#let x = 1 + 2"); roundtrip("#if x [y] #else [z]"); roundtrip("#for x #in y {z}"); + roundtrip("#for k, x #in y {z}"); } }