For loop patterns 🦚

This commit is contained in:
Laurenz 2021-01-26 23:30:03 +01:00
parent 010ddc4795
commit 710f88ccb2
4 changed files with 81 additions and 21 deletions

View File

@ -275,7 +275,7 @@ impl Eval for Spanned<&ExprBinary> {
impl Spanned<&ExprBinary> { impl Spanned<&ExprBinary> {
/// Apply a basic binary operation. /// Apply a basic binary operation.
fn apply<F>(&self, ctx: &mut EvalContext, op: F) -> Value fn apply<F>(self, ctx: &mut EvalContext, op: F) -> Value
where where
F: FnOnce(Value, Value) -> Value, F: FnOnce(Value, Value) -> Value,
{ {
@ -311,7 +311,7 @@ impl Spanned<&ExprBinary> {
} }
/// Apply an assignment operation. /// Apply an assignment operation.
fn assign<F>(&self, ctx: &mut EvalContext, op: F) -> Value fn assign<F>(self, ctx: &mut EvalContext, op: F) -> Value
where where
F: FnOnce(Value, Value) -> Value, F: FnOnce(Value, Value) -> Value,
{ {
@ -379,30 +379,56 @@ impl Eval for Spanned<&ExprFor> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let iter = self.v.iter.eval(ctx); let iter = self.v.iter.eval(ctx);
if let Value::Array(array) = iter { let mut output = if let Expr::Template(_) = self.v.body.v {
let mut output = match self.v.body.v { Value::Template(vec![])
Expr::Template(_) => Value::Template(vec![]), } else {
_ => Value::None, Value::None
}; };
for value in array { macro_rules! iterate {
ctx.scopes.define(self.v.pat.v.as_str(), value); (for ($($binding:ident => $value:ident),*) in $iter:expr) => {
let value = self.v.body.eval(ctx); #[allow(unused_parens)]
for ($($value),*) in $iter {
$(ctx.scopes.define($binding.as_str(), $value);)*
if let Value::Template(prev) = &mut output { let value = self.v.body.eval(ctx);
if let Value::Template(new) = value {
prev.extend(new); 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; (ForPattern::KeyValue(..), Value::Str(_))
} else if iter != Value::Error { | (ForPattern::KeyValue(..), Value::Array(_)) => {
ctx.diag(error!( ctx.diag(error!(self.v.pat.span, "mismatched pattern",));
}
(_, Value::Error) => {}
(_, iter) => ctx.diag(error!(
self.v.iter.span, self.v.iter.span,
"expected array, found {}", "cannot loop over {}",
iter.type_name(), iter.type_name(),
)); )),
} }
Value::Error Value::Error

View File

@ -423,7 +423,7 @@ fn expr_for(p: &mut Parser) -> Option<Expr> {
p.assert(Token::For); p.assert(Token::For);
let mut expr_for = None; 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 p.expect(Token::In) {
if let Some(iter) = p.span_if(expr) { if let Some(iter) = p.span_if(expr) {
if let Some(body) = p.span_if(body) { if let Some(body) = p.span_if(body) {
@ -440,6 +440,17 @@ fn expr_for(p: &mut Parser) -> Option<Expr> {
expr_for expr_for
} }
/// Parse a for loop pattern.
fn for_pattern(p: &mut Parser) -> Option<ForPattern> {
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. /// Parse an identifier.
fn ident(p: &mut Parser) -> Option<Ident> { fn ident(p: &mut Parser) -> Option<Ident> {
match p.peek() { match p.peek() {

View File

@ -528,7 +528,7 @@ impl Pretty for ExprIf {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ExprFor { pub struct ExprFor {
/// The pattern to assign to. /// The pattern to assign to.
pub pat: Spanned<Ident>, pub pat: Spanned<ForPattern>,
/// The expression to iterate over. /// The expression to iterate over.
pub iter: SpanBox<Expr>, pub iter: SpanBox<Expr>,
/// The expression to evaluate for each iteration. /// The expression to evaluate for each iteration.
@ -538,10 +538,32 @@ pub struct ExprFor {
impl Pretty for ExprFor { impl Pretty for ExprFor {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
p.push_str("#for "); p.push_str("#for ");
p.push_str(&self.pat.v); self.pat.v.pretty(p);
p.push_str(" #in "); p.push_str(" #in ");
self.iter.v.pretty(p); self.iter.v.pretty(p);
p.push_str(" "); p.push_str(" ");
self.body.v.pretty(p); 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);
}
}
}
}

View File

@ -127,5 +127,6 @@ mod tests {
roundtrip("#let x = 1 + 2"); roundtrip("#let x = 1 + 2");
roundtrip("#if x [y] #else [z]"); roundtrip("#if x [y] #else [z]");
roundtrip("#for x #in y {z}"); roundtrip("#for x #in y {z}");
roundtrip("#for k, x #in y {z}");
} }
} }