diff options
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/func.rs | 4 | ||||
| -rw-r--r-- | src/eval/mod.rs | 223 |
2 files changed, 142 insertions, 85 deletions
diff --git a/src/eval/func.rs b/src/eval/func.rs index 489527ef..e3b6a99c 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -445,7 +445,9 @@ impl<'a> CapturesVisitor<'a> { match param { ast::Param::Pos(ident) => self.bind(ident), ast::Param::Named(named) => self.bind(named.name()), - ast::Param::Sink(Some(ident)) => self.bind(ident), + ast::Param::Sink(spread) => { + self.bind(spread.name().unwrap_or_default()) + } _ => {} } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 850a3d32..d8f49d66 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -456,6 +456,7 @@ impl Eval for ast::Expr { Self::Unary(v) => v.eval(vm), Self::Binary(v) => v.eval(vm), Self::Let(v) => v.eval(vm), + Self::DestructAssign(v) => v.eval(vm), Self::Set(_) => bail!(forbidden("set")), Self::Show(_) => bail!(forbidden("show")), Self::Conditional(v) => v.eval(vm), @@ -1212,8 +1213,8 @@ impl Eval for ast::Closure { ast::Param::Named(named) => { params.push(Param::Named(named.name(), named.expr().eval(vm)?)); } - ast::Param::Sink(name) => params.push(Param::Sink(name)), - ast::Param::Placeholder => params.push(Param::Placeholder), + ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())), + ast::Param::Placeholder(_) => params.push(Param::Placeholder), } } @@ -1231,98 +1232,142 @@ impl Eval for ast::Closure { } impl ast::Pattern { - // Destruct the given value into the pattern. - #[tracing::instrument(skip_all)] - pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { - match self { - ast::Pattern::Ident(ident) => { - vm.define(ident.clone(), value); - Ok(Value::None) - } - ast::Pattern::Placeholder => Ok(Value::None), - ast::Pattern::Destructuring(destruct) => { - match value { - Value::Array(value) => { - let mut i = 0; - for p in destruct.bindings() { - 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(destruct.bindings().count()).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, sink); - } - 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" - ) - } - ast::DestructuringKind::Placeholder => i += 1, - } - } - if i < value.len() { - bail!(self.span(), "too many elements to destructure"); + fn destruct_array<T>( + &self, + vm: &mut Vm, + value: Array, + f: T, + destruct: &ast::Destructuring, + ) -> SourceResult<Value> + where + T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, + { + let mut i = 0; + for p in destruct.bindings() { + match p { + ast::DestructuringKind::Normal(expr) => { + let Ok(v) = value.at(i) else { + bail!(expr.span(), "not enough elements to destructure"); + }; + f(vm, expr, v.clone())?; + i += 1; + } + ast::DestructuringKind::Sink(spread) => { + let sink_size = (1 + value.len() as usize) + .checked_sub(destruct.bindings().count()); + let sink = + sink_size.and_then(|s| value.slice(i, Some(i + s as i64)).ok()); + + if let (Some(sink_size), Some(sink)) = (sink_size, sink) { + if let Some(expr) = spread.expr() { + f(vm, expr, Value::Array(sink.clone()))?; } + i += sink_size as i64; + } else { + bail!(self.span(), "not enough elements to destructure") } - Value::Dict(value) => { - let mut sink = None; - let mut used = HashSet::new(); - for p in destruct.bindings() { - match p { - ast::DestructuringKind::Ident(ident) => { - let Ok(v) = value.at(&ident) else { + } + ast::DestructuringKind::Named(named) => { + bail!(named.span(), "cannot destructure named elements from an array") + } + ast::DestructuringKind::Placeholder(_) => i += 1, + } + } + if i < value.len() { + bail!(self.span(), "too many elements to destructure"); + } + + Ok(Value::None) + } + + fn destruct_dict<T>( + &self, + vm: &mut Vm, + value: Dict, + f: T, + destruct: &ast::Destructuring, + ) -> SourceResult<Value> + where + T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, + { + let mut sink = None; + let mut used = HashSet::new(); + for p in destruct.bindings() { + match p { + ast::DestructuringKind::Normal(ast::Expr::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"); + f(vm, ast::Expr::Ident(ident.clone()), v.clone())?; + used.insert(ident.take()); + } + ast::DestructuringKind::Sink(spread) => sink = spread.expr(), + ast::DestructuringKind::Named(named) => { + let Ok(v) = value.at(named.name().as_str()) else { + bail!(named.name().span(), "destructuring key not found in dictionary"); }; - vm.define(ident.clone(), v.clone()); - used.insert(key.clone().take()); - } - ast::DestructuringKind::Placeholder => {} - } - } + f(vm, named.expr(), v.clone())?; + used.insert(named.name().take()); + } + ast::DestructuringKind::Placeholder(_) => {} + ast::DestructuringKind::Normal(expr) => { + bail!(expr.span(), "expected key, found expression"); + } + } + } - 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()); - } + if let Some(expr) = sink { + let mut sink = Dict::new(); + for (key, value) in value { + if !used.contains(key.as_str()) { + sink.insert(key, value); } + } + f(vm, expr, Value::Dict(sink))?; + } + + Ok(Value::None) + } + /// Destruct the given value into the pattern and apply the function to each binding. + #[tracing::instrument(skip_all)] + fn apply<T>(&self, vm: &mut Vm, value: Value, f: T) -> SourceResult<Value> + where + T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, + { + match self { + ast::Pattern::Normal(expr) => { + f(vm, expr.clone(), value)?; Ok(Value::None) } + ast::Pattern::Placeholder(_) => Ok(Value::None), + ast::Pattern::Destructuring(destruct) => match value { + Value::Array(value) => self.destruct_array(vm, value, f, destruct), + Value::Dict(value) => self.destruct_dict(vm, value, f, destruct), + _ => bail!(self.span(), "cannot destructure {}", value.type_name()), + }, } } + + /// Destruct the value into the pattern by binding. + pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { + self.apply(vm, value, |vm, expr, value| match expr { + ast::Expr::Ident(ident) => { + vm.define(ident, value); + Ok(Value::None) + } + _ => unreachable!(), + }) + } + + /// Destruct the value into the pattern by assignment. + pub fn assign(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { + self.apply(vm, value, |vm, expr, value| { + let location = expr.access(vm)?; + *location = value; + Ok(Value::None) + }) + } } impl Eval for ast::LetBinding { @@ -1345,6 +1390,16 @@ impl Eval for ast::LetBinding { } } +impl Eval for ast::DestructAssignment { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + let value = self.value().eval(vm)?; + self.pattern().assign(vm, value)?; + Ok(Value::None) + } +} + impl Eval for ast::SetRule { type Output = Styles; @@ -1514,7 +1569,7 @@ impl Eval for ast::ForLoop { let pattern = self.pattern(); match (&pattern, iter.clone()) { - (ast::Pattern::Ident(_), Value::Str(string)) => { + (ast::Pattern::Normal(_), Value::Str(string)) => { // Iterate over graphemes of string. iter!(for pattern in string.as_str().graphemes(true)); } @@ -1526,7 +1581,7 @@ impl Eval for ast::ForLoop { // Iterate over values of array. iter!(for pattern in array); } - (ast::Pattern::Ident(_), _) => { + (ast::Pattern::Normal(_), _) => { bail!(self.iter().span(), "cannot loop over {}", iter.type_name()); } (_, _) => { |
