summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/array.rs11
-rw-r--r--src/eval/func.rs14
-rw-r--r--src/eval/methods.rs2
-rw-r--r--src/eval/mod.rs151
4 files changed, 139 insertions, 39 deletions
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<Value> {
+ 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() {