From 0f8219b392e96d3cf7d784ee5d474274169d9918 Mon Sep 17 00:00:00 2001 From: Marmare314 <49279081+Marmare314@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:26:09 +0200 Subject: Unpacking syntax (#532) Closes #341 --- src/eval/array.rs | 11 ++++ src/eval/func.rs | 14 +++-- src/eval/methods.rs | 2 + src/eval/mod.rs | 151 ++++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 139 insertions(+), 39 deletions(-) (limited to 'src/eval') diff --git a/src/eval/array.rs b/src/eval/array.rs index 8dcbd5a9..394191ea 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -322,6 +322,17 @@ impl Array { usize::try_from(if index >= 0 { index } else { self.len().checked_add(index)? }) .ok() } + + /// Enumerate all items in the array. + pub fn enumerate(&self) -> Self { + let v = self + .iter() + .enumerate() + .map(|(i, value)| array![i, value.clone()]) + .map(Value::Array) + .collect(); + Self::from_vec(v) + } } impl Debug for Array { diff --git a/src/eval/func.rs b/src/eval/func.rs index e6402e87..93da6d7b 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -441,7 +441,10 @@ impl<'a> CapturesVisitor<'a> { if let Some(init) = expr.init() { self.visit(init.as_untyped()); } - self.bind(expr.binding()); + + for ident in expr.kind().idents() { + self.bind(ident); + } } // A for loop contains one or two bindings in its pattern. These are @@ -450,11 +453,12 @@ impl<'a> CapturesVisitor<'a> { Some(ast::Expr::For(expr)) => { self.visit(expr.iter().as_untyped()); self.internal.enter(); + let pattern = expr.pattern(); - if let Some(key) = pattern.key() { - self.bind(key); + for ident in pattern.idents() { + self.bind(ident); } - self.bind(pattern.value()); + self.visit(expr.body().as_untyped()); self.internal.exit(); } @@ -550,7 +554,7 @@ mod tests { // For loop. test("#for x in y { x + z }", &["y", "z"]); - test("#for x, y in y { x + y }", &["y"]); + test("#for (x, y) in y { x + y }", &["y"]); test("#for x in y {} #x", &["x", "y"]); // Import. diff --git a/src/eval/methods.rs b/src/eval/methods.rs index b88bca50..8b364fcb 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -116,6 +116,7 @@ pub fn call( array.join(sep, last).at(span)? } "sorted" => Value::Array(array.sorted().at(span)?), + "enumerate" => Value::Array(array.enumerate()), _ => return missing(), }, @@ -297,6 +298,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ("rev", false), ("slice", true), ("sorted", false), + ("enumerate", false), ], "dictionary" => &[ ("at", true), diff --git a/src/eval/mod.rs b/src/eval/mod.rs index e278d787..ca69b2d8 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -37,7 +37,7 @@ pub use self::value::*; pub(crate) use self::methods::methods_on; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; use std::mem; use std::path::{Path, PathBuf}; @@ -1184,6 +1184,97 @@ impl Eval for ast::Closure { } } +impl ast::Pattern { + // Destruct the given value into the pattern. + pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult { + match self.kind() { + ast::PatternKind::Ident(ident) => { + vm.define(ident, value); + Ok(Value::None) + } + ast::PatternKind::Destructure(pattern) => { + match value { + Value::Array(value) => { + let mut i = 0; + for p in &pattern { + match p { + ast::DestructuringKind::Ident(ident) => { + let Ok(v) = value.at(i) else { + bail!(ident.span(), "not enough elements to destructure"); + }; + vm.define(ident.clone(), v.clone()); + i += 1; + } + ast::DestructuringKind::Sink(ident) => { + (1 + value.len() as usize).checked_sub(pattern.len()).and_then(|sink_size| { + let Ok(sink) = value.slice(i, Some(i + sink_size as i64)) else { + return None; + }; + if let Some(ident) = ident { + vm.define(ident.clone(), sink.clone()); + } + i += sink_size as i64; + Some(()) + }).ok_or("not enough elements to destructure").at(self.span())?; + } + ast::DestructuringKind::Named(key, _) => { + bail!( + key.span(), + "cannot destructure named elements from an array" + ) + } + } + } + if i < value.len() as i64 { + bail!(self.span(), "too many elements to destructure"); + } + } + Value::Dict(value) => { + let mut sink = None; + let mut used = HashSet::new(); + for p in &pattern { + match p { + ast::DestructuringKind::Ident(ident) => { + let Ok(v) = value.at(ident) else { + bail!(ident.span(), "destructuring key not found in dictionary"); + }; + vm.define(ident.clone(), v.clone()); + used.insert(ident.clone().take()); + } + ast::DestructuringKind::Sink(ident) => { + sink = ident.clone() + } + ast::DestructuringKind::Named(key, ident) => { + let Ok(v) = value.at(key) else { + bail!(ident.span(), "destructuring key not found in dictionary"); + }; + vm.define(ident.clone(), v.clone()); + used.insert(key.clone().take()); + } + } + } + + if let Some(ident) = sink { + let mut sink = Dict::new(); + for (key, value) in value { + if !used.contains(key.as_str()) { + sink.insert(key, value); + } + } + vm.define(ident, Value::Dict(sink)); + } + } + _ => { + bail!(self.span(), "cannot destructure {}", value.type_name()); + } + } + + Ok(Value::None) + } + } + } +} + impl Eval for ast::LetBinding { type Output = Value; @@ -1192,8 +1283,14 @@ impl Eval for ast::LetBinding { Some(expr) => expr.eval(vm)?, None => Value::None, }; - vm.define(self.binding(), value); - Ok(Value::None) + + match self.kind() { + ast::LetBindingKind::Normal(pattern) => pattern.define(vm, value), + ast::LetBindingKind::Closure(ident) => { + vm.define(ident, value); + Ok(Value::None) + } + } } } @@ -1333,12 +1430,12 @@ impl Eval for ast::ForLoop { let mut output = Value::None; macro_rules! iter { - (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ + (for $pat:ident in $iter:expr) => {{ vm.scopes.enter(); #[allow(unused_parens)] - for ($($value),*) in $iter { - $(vm.define($binding.clone(), $value);)* + for value in $iter { + $pat.define(vm, Value::from(value))?; let body = self.body(); let value = body.eval(vm)?; @@ -1361,40 +1458,26 @@ impl Eval for ast::ForLoop { let iter = self.iter().eval(vm)?; let pattern = self.pattern(); - let key = pattern.key(); - let value = pattern.value(); - match (key, value, iter) { - (None, v, Value::Str(string)) => { - iter!(for (v => value) in string.as_str().graphemes(true)); + match (pattern.kind(), iter.clone()) { + (ast::PatternKind::Ident(_), Value::Str(string)) => { + // iterate over characters of string + iter!(for pattern in string.as_str().graphemes(true)); } - (None, v, Value::Array(array)) => { - iter!(for (v => value) in array.into_iter()); + (_, Value::Dict(dict)) => { + // iterate over keys of dict + iter!(for pattern in dict.pairs()); } - (Some(i), v, Value::Array(array)) => { - iter!(for (i => idx, v => value) in array.into_iter().enumerate()); + (_, Value::Array(array)) => { + // iterate over values of array and allow destructuring + iter!(for pattern in array.into_iter()); } - (None, v, Value::Dict(dict)) => { - iter!(for (v => value) in dict.into_iter().map(|p| p.1)); - } - (Some(k), v, Value::Dict(dict)) => { - iter!(for (k => key, v => value) in dict.into_iter()); - } - (None, v, Value::Args(args)) => { - iter!(for (v => value) in args.items.into_iter() - .filter(|arg| arg.name.is_none()) - .map(|arg| arg.value.v)); - } - (Some(k), v, Value::Args(args)) => { - iter!(for (k => key, v => value) in args.items.into_iter() - .map(|arg| (arg.name.map_or(Value::None, Value::Str), arg.value.v))); - } - (_, _, Value::Str(_)) => { - bail!(pattern.span(), "mismatched pattern"); - } - (_, _, iter) => { + (ast::PatternKind::Ident(_), _) => { bail!(self.iter().span(), "cannot loop over {}", iter.type_name()); } + (_, _) => { + bail!(pattern.span(), "cannot destructure values of {}", iter.type_name()) + } } if flow.is_some() { -- cgit v1.2.3