diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-01-22 17:16:42 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-01-22 17:16:42 +0100 |
| commit | ac788f2082711161ec8208eede04d9a2bae02241 (patch) | |
| tree | b139e41d327af906163c0b177d402b855c04507e /src/eval | |
| parent | 0de4f3ed7bb20a94fd58f93b0793d3b5a8e13972 (diff) | |
Many more expressions 🥗
Boolean, equality, comparison and assignment expression parsing and evaluation.
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/call.rs | 36 | ||||
| -rw-r--r-- | src/eval/mod.rs | 134 | ||||
| -rw-r--r-- | src/eval/ops.rs | 177 | ||||
| -rw-r--r-- | src/eval/scope.rs | 33 | ||||
| -rw-r--r-- | src/eval/value.rs | 12 |
5 files changed, 252 insertions, 140 deletions
diff --git a/src/eval/call.rs b/src/eval/call.rs index d57ed144..7b45c09a 100644 --- a/src/eval/call.rs +++ b/src/eval/call.rs @@ -1,32 +1,26 @@ use super::*; -use crate::diag::Deco; impl Eval for Spanned<&ExprCall> { type Output = Value; fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let name = &self.v.name.v; - let span = self.v.name.span; - - if let Some(value) = ctx.scopes.get(name) { - if let Value::Func(func) = value { - let func = func.clone(); - ctx.deco(Deco::Resolved.with_span(span)); - - let mut args = self.v.args.as_ref().eval(ctx); - let returned = func(ctx, &mut args); - args.finish(ctx); - - return returned; - } else { - let ty = value.type_name(); - ctx.diag(error!(span, "expected function, found {}", ty)); - } - } else if !name.is_empty() { - ctx.diag(error!(span, "unknown function")); + let callee = self.v.callee.eval(ctx); + + if let Value::Func(func) = callee { + let func = func.clone(); + let mut args = self.v.args.as_ref().eval(ctx); + let returned = func(ctx, &mut args); + args.finish(ctx); + + return returned; + } else if callee != Value::Error { + ctx.diag(error!( + self.v.callee.span, + "expected function, found {}", + callee.type_name(), + )); } - ctx.deco(Deco::Unresolved.with_span(span)); Value::Error } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 54fe2324..c604f2d0 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -164,13 +164,13 @@ impl Eval for Spanned<&Expr> { Value::Error } }, - Expr::Bool(v) => Value::Bool(*v), - Expr::Int(v) => Value::Int(*v), - Expr::Float(v) => Value::Float(*v), - Expr::Length(v, unit) => Value::Length(Length::with_unit(*v, *unit)), - Expr::Angle(v, unit) => Value::Angle(Angle::with_unit(*v, *unit)), - Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)), - Expr::Color(v) => Value::Color(Color::Rgba(*v)), + &Expr::Bool(v) => Value::Bool(v), + &Expr::Int(v) => Value::Int(v), + &Expr::Float(v) => Value::Float(v), + &Expr::Length(v, unit) => Value::Length(Length::with_unit(v, unit)), + &Expr::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)), + &Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)), + &Expr::Color(v) => Value::Color(Color::Rgba(v)), Expr::Str(v) => Value::Str(v.clone()), Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)), Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)), @@ -210,16 +210,27 @@ impl Eval for Spanned<&ExprUnary> { fn eval(self, ctx: &mut EvalContext) -> Self::Output { let value = self.v.expr.as_ref().eval(ctx); - - if let Value::Error = value { + if value == Value::Error { return Value::Error; } - let span = self.v.op.span.join(self.v.expr.span); - match self.v.op.v { - UnOp::Pos => ops::pos(ctx, span, value), - UnOp::Neg => ops::neg(ctx, span, value), + let ty = value.type_name(); + let out = match self.v.op.v { + UnOp::Pos => ops::pos(value), + UnOp::Neg => ops::neg(value), + UnOp::Not => ops::not(value), + }; + + if out == Value::Error { + ctx.diag(error!( + self.span, + "cannot apply '{}' to {}", + self.v.op.v.as_str(), + ty, + )); } + + out } } @@ -227,20 +238,90 @@ impl Eval for Spanned<&ExprBinary> { type Output = Value; fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let lhs = self.v.lhs.as_ref().eval(ctx); - let rhs = self.v.rhs.as_ref().eval(ctx); + match self.v.op.v { + BinOp::Add => self.apply(ctx, ops::add), + BinOp::Sub => self.apply(ctx, ops::sub), + BinOp::Mul => self.apply(ctx, ops::mul), + BinOp::Div => self.apply(ctx, ops::div), + BinOp::And => self.apply(ctx, ops::and), + BinOp::Or => self.apply(ctx, ops::or), + BinOp::Eq => self.apply(ctx, ops::eq), + BinOp::Neq => self.apply(ctx, ops::neq), + BinOp::Lt => self.apply(ctx, ops::lt), + BinOp::Leq => self.apply(ctx, ops::leq), + BinOp::Gt => self.apply(ctx, ops::gt), + BinOp::Geq => self.apply(ctx, ops::geq), + BinOp::Assign => self.assign(ctx, |_, b| b), + BinOp::AddAssign => self.assign(ctx, ops::add), + BinOp::SubAssign => self.assign(ctx, ops::sub), + BinOp::MulAssign => self.assign(ctx, ops::mul), + BinOp::DivAssign => self.assign(ctx, ops::div), + } + } +} + +impl Spanned<&ExprBinary> { + /// Apply a basic binary operation. + fn apply<F>(&self, ctx: &mut EvalContext, op: F) -> Value + where + F: FnOnce(Value, Value) -> Value, + { + let lhs = self.v.lhs.eval(ctx); + + // Short-circuit boolean operations. + match (self.v.op.v, &lhs) { + (BinOp::And, Value::Bool(false)) => return Value::Bool(false), + (BinOp::Or, Value::Bool(true)) => return Value::Bool(true), + _ => {} + } + + let rhs = self.v.rhs.eval(ctx); if lhs == Value::Error || rhs == Value::Error { return Value::Error; } - let span = self.v.lhs.span.join(self.v.rhs.span); - match self.v.op.v { - BinOp::Add => ops::add(ctx, span, lhs, rhs), - BinOp::Sub => ops::sub(ctx, span, lhs, rhs), - BinOp::Mul => ops::mul(ctx, span, lhs, rhs), - BinOp::Div => ops::div(ctx, span, lhs, rhs), + let lhty = lhs.type_name(); + let rhty = rhs.type_name(); + let out = op(lhs, rhs); + if out == Value::Error { + ctx.diag(error!( + self.span, + "cannot apply '{}' to {} and {}", + self.v.op.v.as_str(), + lhty, + rhty, + )); + } + + out + } + + /// Apply an assignment operation. + fn assign<F>(&self, ctx: &mut EvalContext, op: F) -> Value + where + F: FnOnce(Value, Value) -> Value, + { + let rhs = self.v.rhs.eval(ctx); + let span = self.v.lhs.span; + + if let Expr::Ident(id) = &self.v.lhs.v { + if let Some(slot) = ctx.scopes.get_mut(id) { + let lhs = std::mem::replace(slot, Value::None); + *slot = op(lhs, rhs); + return Value::None; + } else { + if ctx.scopes.is_const(id) { + ctx.diag(error!(span, "cannot assign to constant")); + } else { + ctx.diag(error!(span, "unknown variable")); + } + } + } else { + ctx.diag(error!(span, "cannot assign to this expression")); } + + Value::Error } } @@ -263,20 +344,21 @@ impl Eval for Spanned<&ExprIf> { fn eval(self, ctx: &mut EvalContext) -> Self::Output { let condition = self.v.condition.eval(ctx); if let Value::Bool(boolean) = condition { - if boolean { + return if boolean { self.v.if_body.eval(ctx) } else if let Some(expr) = &self.v.else_body { expr.eval(ctx) } else { Value::None - } - } else { + }; + } else if condition != Value::Error { ctx.diag(error!( self.v.condition.span, "expected boolean, found {}", - condition.type_name() + condition.type_name(), )); - Value::Error } + + Value::Error } } diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 0a273da5..939445f0 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,22 +1,21 @@ use super::*; +use Value::*; -/// Apply plus operator to a value. -pub fn pos(ctx: &mut EvalContext, span: Span, value: Value) -> Value { - if value.is_numeric() { - value - } else { - ctx.diag(error!( - span, - "cannot apply plus operator to {}", - value.type_name() - )); - Value::Error +/// Apply the plus operator to a value. +pub fn pos(value: Value) -> Value { + match value { + Int(v) => Int(v), + Float(v) => Float(v), + Length(v) => Length(v), + Angle(v) => Angle(v), + Relative(v) => Relative(v), + Linear(v) => Linear(v), + _ => Error, } } /// Compute the negation of a value. -pub fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value { - use Value::*; +pub fn neg(value: Value) -> Value { match value { Int(v) => Int(-v), Float(v) => Float(-v), @@ -24,18 +23,13 @@ pub fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value { Angle(v) => Angle(-v), Relative(v) => Relative(-v), Linear(v) => Linear(-v), - v => { - ctx.diag(error!(span, "cannot negate {}", v.type_name())); - Value::Error - } + _ => Error, } } /// Compute the sum of two values. -pub fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { - use Value::*; +pub fn add(lhs: Value, rhs: Value) -> Value { match (lhs, rhs) { - // Numeric types to themselves. (Int(a), Int(b)) => Int(a + b), (Int(a), Float(b)) => Float(a as f64 + b), (Float(a), Int(b)) => Float(a + b as f64), @@ -50,30 +44,17 @@ pub fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { (Linear(a), Length(b)) => Linear(a + b), (Linear(a), Relative(b)) => Linear(a + b), (Linear(a), Linear(b)) => Linear(a + b), - - // Complex data types to themselves. (Str(a), Str(b)) => Str(a + &b), (Array(a), Array(b)) => Array(concat(a, b)), (Dict(a), Dict(b)) => Dict(concat(a, b)), (Template(a), Template(b)) => Template(concat(a, b)), - - (a, b) => { - ctx.diag(error!( - span, - "cannot add {} and {}", - a.type_name(), - b.type_name() - )); - Value::Error - } + _ => Error, } } /// Compute the difference of two values. -pub fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { - use Value::*; +pub fn sub(lhs: Value, rhs: Value) -> Value { match (lhs, rhs) { - // Numbers from themselves. (Int(a), Int(b)) => Int(a - b), (Int(a), Float(b)) => Float(a as f64 - b), (Float(a), Int(b)) => Float(a - b as f64), @@ -88,24 +69,13 @@ pub fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { (Linear(a), Length(b)) => Linear(a - b), (Linear(a), Relative(b)) => Linear(a - b), (Linear(a), Linear(b)) => Linear(a - b), - - (a, b) => { - ctx.diag(error!( - span, - "cannot subtract {1} from {0}", - a.type_name(), - b.type_name() - )); - Value::Error - } + _ => Error, } } /// Compute the product of two values. -pub fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { - use Value::*; +pub fn mul(lhs: Value, rhs: Value) -> Value { match (lhs, rhs) { - // Numeric types with numbers. (Int(a), Int(b)) => Int(a * b), (Int(a), Float(b)) => Float(a as f64 * b), (Float(a), Int(b)) => Float(a * b as f64), @@ -126,28 +96,13 @@ pub fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { (Linear(a), Float(b)) => Linear(a * b), (Int(a), Linear(b)) => Linear(a as f64 * b), (Float(a), Linear(b)) => Linear(a * b), - - // Integers with strings. - (Int(a), Str(b)) => Str(b.repeat(0.max(a) as usize)), - (Str(a), Int(b)) => Str(a.repeat(0.max(b) as usize)), - - (a, b) => { - ctx.diag(error!( - span, - "cannot multiply {} with {}", - a.type_name(), - b.type_name() - )); - Value::Error - } + _ => Error, } } /// Compute the quotient of two values. -pub fn div(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { - use Value::*; +pub fn div(lhs: Value, rhs: Value) -> Value { match (lhs, rhs) { - // Numeric types by numbers. (Int(a), Int(b)) => Float(a as f64 / b as f64), (Int(a), Float(b)) => Float(a as f64 / b), (Float(a), Int(b)) => Float(a / b as f64), @@ -160,19 +115,93 @@ pub fn div(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { (Relative(a), Float(b)) => Relative(a / b), (Linear(a), Int(b)) => Linear(a / b as f64), (Linear(a), Float(b)) => Linear(a / b), + _ => Error, + } +} - (a, b) => { - ctx.diag(error!( - span, - "cannot divide {} by {}", - a.type_name(), - b.type_name() - )); - Value::Error - } +/// Compute the logical "not" of a value. +pub fn not(value: Value) -> Value { + match value { + Bool(b) => Bool(!b), + _ => Error, } } +/// Compute the logical "and" of two values. +pub fn and(lhs: Value, rhs: Value) -> Value { + match (lhs, rhs) { + (Bool(a), Bool(b)) => Bool(a && b), + _ => Error, + } +} + +/// Compute the logical "or" of two values. +pub fn or(lhs: Value, rhs: Value) -> Value { + match (lhs, rhs) { + (Bool(a), Bool(b)) => Bool(a || b), + _ => Error, + } +} + +/// Compute whether two values are equal. +pub fn eq(lhs: Value, rhs: Value) -> Value { + Bool(value_eq(&lhs, &rhs)) +} + +/// Compute whether two values are equal. +pub fn neq(lhs: Value, rhs: Value) -> Value { + Bool(!value_eq(&lhs, &rhs)) +} + +/// Recursively compute whether two values are equal. +fn value_eq(lhs: &Value, rhs: &Value) -> bool { + match (lhs, rhs) { + (&Int(a), &Float(b)) => a as f64 == b, + (&Float(a), &Int(b)) => a == b as f64, + (&Length(a), &Linear(b)) => a == b.abs && b.rel.is_zero(), + (&Relative(a), &Linear(b)) => a == b.rel && b.abs.is_zero(), + (&Linear(a), &Length(b)) => a.abs == b && a.rel.is_zero(), + (&Linear(a), &Relative(b)) => a.rel == b && a.abs.is_zero(), + (Array(a), Array(b)) => array_eq(a, b), + (Dict(a), Dict(b)) => dict_eq(a, b), + (Template(a), Template(b)) => Span::without_cmp(|| a == b), + (a, b) => a == b, + } +} + +/// Compute whether two arrays are equal. +fn array_eq(a: &ValueArray, b: &ValueArray) -> bool { + a.len() == b.len() && a.iter().zip(b).all(|(x, y)| value_eq(x, y)) +} + +/// Compute whether two dictionaries are equal. +fn dict_eq(a: &ValueDict, b: &ValueDict) -> bool { + a.len() == b.len() + && a.iter().all(|(k, x)| b.get(k).map_or(false, |y| value_eq(x, y))) +} + +macro_rules! comparison { + ($name:ident, $op:tt) => { + /// Compute how a value compares with another value. + pub fn $name(lhs: Value, rhs: Value) -> Value { + match (lhs, rhs) { + (Int(a), Int(b)) => Bool(a $op b), + (Int(a), Float(b)) => Bool((a as f64) $op b), + (Float(a), Int(b)) => Bool(a $op b as f64), + (Float(a), Float(b)) => Bool(a $op b), + (Angle(a), Angle(b)) => Bool(a $op b), + (Length(a), Length(b)) => Bool(a $op b), + _ => Error, + } + } + }; +} + +comparison!(lt, <); +comparison!(leq, <=); +comparison!(gt, >); +comparison!(geq, >=); + /// Concatenate two collections. fn concat<T, A>(mut a: T, b: T) -> T where diff --git a/src/eval/scope.rs b/src/eval/scope.rs index a93de269..ed4edbb9 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -21,7 +21,12 @@ impl<'a> Scopes<'a> { Self { top: Scope::new(), scopes: vec![], base } } - /// Look up the value of a variable in the scopes. + /// Define a variable in the active scope. + pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) { + self.top.define(var, value); + } + + /// Look up the value of a variable. pub fn get(&self, var: &str) -> Option<&Value> { iter::once(&self.top) .chain(&self.scopes) @@ -29,9 +34,18 @@ impl<'a> Scopes<'a> { .find_map(|scope| scope.get(var)) } - /// Define a variable in the active scope. - pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) { - self.top.set(var, value); + /// Get a mutable reference to a variable. + pub fn get_mut(&mut self, var: &str) -> Option<&mut Value> { + iter::once(&mut self.top) + .chain(&mut self.scopes) + .find_map(|scope| scope.get_mut(var)) + } + + /// Return whether the variable is constant (not writable). + /// + /// Defaults to `false` if the variable does not exist. + pub fn is_const(&self, var: &str) -> bool { + self.base.get(var).is_some() } } @@ -47,14 +61,19 @@ impl Scope { Self::default() } + /// Define a new variable. + pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) { + self.values.insert(var.into(), value.into()); + } + /// Look up the value of a variable. pub fn get(&self, var: &str) -> Option<&Value> { self.values.get(var) } - /// Store the value for a variable. - pub fn set(&mut self, var: impl Into<String>, value: impl Into<Value>) { - self.values.insert(var.into(), value.into()); + /// Get a mutable reference to a variable. + pub fn get_mut(&mut self, var: &str) -> Option<&mut Value> { + self.values.get_mut(var) } } diff --git a/src/eval/value.rs b/src/eval/value.rs index 20cc457c..6fa70206 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -77,18 +77,6 @@ impl Value { Self::Error => "error", } } - - /// Whether the value is numeric. - pub fn is_numeric(&self) -> bool { - matches!(self, - Value::Int(_) - | Value::Float(_) - | Value::Length(_) - | Value::Angle(_) - | Value::Relative(_) - | Value::Linear(_) - ) - } } impl Eval for &Value { |
