summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-01-22 17:16:42 +0100
committerLaurenz <laurmaedje@gmail.com>2021-01-22 17:16:42 +0100
commitac788f2082711161ec8208eede04d9a2bae02241 (patch)
treeb139e41d327af906163c0b177d402b855c04507e /src/eval
parent0de4f3ed7bb20a94fd58f93b0793d3b5a8e13972 (diff)
Many more expressions 🥗
Boolean, equality, comparison and assignment expression parsing and evaluation.
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/call.rs36
-rw-r--r--src/eval/mod.rs134
-rw-r--r--src/eval/ops.rs177
-rw-r--r--src/eval/scope.rs33
-rw-r--r--src/eval/value.rs12
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 {