summaryrefslogtreecommitdiff
path: root/src
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
parent0de4f3ed7bb20a94fd58f93b0793d3b5a8e13972 (diff)
Many more expressions 🥗
Boolean, equality, comparison and assignment expression parsing and evaluation.
Diffstat (limited to 'src')
-rw-r--r--src/diag.rs6
-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
-rw-r--r--src/geom/length.rs5
-rw-r--r--src/geom/linear.rs6
-rw-r--r--src/geom/relative.rs7
-rw-r--r--src/library/mod.rs4
-rw-r--r--src/library/style.rs2
-rw-r--r--src/parse/collection.rs4
-rw-r--r--src/parse/mod.rs179
-rw-r--r--src/syntax/expr.rs349
-rw-r--r--src/syntax/span.rs35
15 files changed, 630 insertions, 359 deletions
diff --git a/src/diag.rs b/src/diag.rs
index a7bea6f1..fca905da 100644
--- a/src/diag.rs
+++ b/src/diag.rs
@@ -93,12 +93,6 @@ pub enum Deco {
Strong,
/// Emphasized text.
Emph,
- /// A valid, successfully resolved name.
- Resolved,
- /// An invalid, unresolved name.
- Unresolved,
- /// A name in a dictionary or argument list.
- Name,
}
/// Construct a diagnostic with [`Error`](Level::Error) level.
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 {
diff --git a/src/geom/length.rs b/src/geom/length.rs
index 1a45c63c..db28761b 100644
--- a/src/geom/length.rs
+++ b/src/geom/length.rs
@@ -81,6 +81,11 @@ impl Length {
Self { raw: self.raw.max(other.raw) }
}
+ /// Whether the length is zero.
+ pub fn is_zero(self) -> bool {
+ self.raw == 0.0
+ }
+
/// Whether the length is finite.
pub fn is_finite(self) -> bool {
self.raw.is_finite()
diff --git a/src/geom/linear.rs b/src/geom/linear.rs
index 5638517b..05990096 100644
--- a/src/geom/linear.rs
+++ b/src/geom/linear.rs
@@ -26,9 +26,9 @@ impl Linear {
self.rel.resolve(length) + self.abs
}
- /// Whether this linear's relative part is zero.
- pub fn is_absolute(self) -> bool {
- self.rel == Relative::ZERO
+ /// Whether both parts are zero.
+ pub fn is_zero(self) -> bool {
+ self.rel.is_zero() && self.abs.is_zero()
}
}
diff --git a/src/geom/relative.rs b/src/geom/relative.rs
index e91ea672..cea7e4e3 100644
--- a/src/geom/relative.rs
+++ b/src/geom/relative.rs
@@ -27,12 +27,17 @@ impl Relative {
/// Resolve this relative to the given `length`.
pub fn resolve(self, length: Length) -> Length {
// Zero wins over infinity.
- if self.0 == 0.0 {
+ if self.is_zero() {
Length::ZERO
} else {
self.get() * length
}
}
+
+ /// Whether the ratio is zero.
+ pub fn is_zero(self) -> bool {
+ self.0 == 0.0
+ }
}
impl Display for Relative {
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 5c4e774c..ed83270d 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -23,10 +23,10 @@ pub fn new() -> Scope {
let mut std = Scope::new();
macro_rules! set {
(func: $name:expr, $func:expr) => {
- std.set($name, ValueFunc::new($name, $func))
+ std.define($name, ValueFunc::new($name, $func))
};
(any: $var:expr, $any:expr) => {
- std.set($var, ValueAny::new($any))
+ std.define($var, ValueAny::new($any))
};
}
diff --git a/src/library/style.rs b/src/library/style.rs
index ee52c97c..670104d6 100644
--- a/src/library/style.rs
+++ b/src/library/style.rs
@@ -58,7 +58,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value {
let snapshot = ctx.state.clone();
if let Some(linear) = args.find::<Linear>(ctx) {
- if linear.is_absolute() {
+ if linear.rel.is_zero() {
ctx.state.font.size = linear.abs;
ctx.state.font.scale = Relative::ONE.into();
} else {
diff --git a/src/parse/collection.rs b/src/parse/collection.rs
index 58fd91ae..95ca9847 100644
--- a/src/parse/collection.rs
+++ b/src/parse/collection.rs
@@ -1,5 +1,4 @@
use super::*;
-use crate::diag::Deco;
/// Parse the arguments to a function call.
pub fn arguments(p: &mut Parser) -> ExprArgs {
@@ -54,9 +53,8 @@ fn argument(p: &mut Parser) -> Option<Argument> {
let first = p.span_if(expr)?;
if p.eat_if(Token::Colon) {
if let Expr::Ident(ident) = first.v {
- let expr = p.span_if(expr)?;
let name = ident.with_span(first.span);
- p.deco(Deco::Name.with_span(name.span));
+ let expr = p.span_if(expr)?;
Some(Argument::Named(Named { name, expr }))
} else {
p.diag(error!(first.span, "expected identifier"));
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 622223fa..00512c3f 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -49,7 +49,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
let node = match p.peek()? {
// Bracket call.
Token::LeftBracket => {
- return Some(Node::Expr(bracket_call(p)));
+ return Some(Node::Expr(bracket_call(p)?));
}
// Code block.
@@ -153,22 +153,30 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String {
}
/// Parse a bracketed function call.
-fn bracket_call(p: &mut Parser) -> Expr {
+fn bracket_call(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Bracket, TokenMode::Code);
// One header is guaranteed, but there may be more (through chaining).
let mut outer = vec![];
- let mut inner = p.span(bracket_subheader);
+ let mut inner = p.span_if(bracket_subheader);
while p.eat_if(Token::Pipe) {
- outer.push(inner);
- inner = p.span(bracket_subheader);
+ if let Some(new) = p.span_if(bracket_subheader) {
+ outer.extend(inner);
+ inner = Some(new);
+ }
}
p.end_group();
- if p.peek() == Some(Token::LeftBracket) {
- let body = p.span(|p| Expr::Template(bracket_body(p)));
+ let body = if p.peek() == Some(Token::LeftBracket) {
+ Some(p.span(|p| Expr::Template(bracket_body(p))))
+ } else {
+ None
+ };
+
+ let mut inner = inner?;
+ if let Some(body) = body {
inner.span.expand(body.span);
inner.v.args.v.push(Argument::Pos(body));
}
@@ -181,28 +189,25 @@ fn bracket_call(p: &mut Parser) -> Expr {
inner = top;
}
- Expr::Call(inner.v)
+ Some(Expr::Call(inner.v))
}
/// Parse one subheader of a bracketed function call.
-fn bracket_subheader(p: &mut Parser) -> ExprCall {
+fn bracket_subheader(p: &mut Parser) -> Option<ExprCall> {
p.start_group(Group::Subheader, TokenMode::Code);
- let start = p.next_start();
- let name = p.span_if(ident).unwrap_or_else(|| {
- let what = "function name";
- if p.eof() {
- p.expected_at(what, start);
- } else {
- p.expected(what);
- }
- Ident(String::new()).with_span(start)
- });
+ let name = p.span_if(ident);
+ if name.is_none() {
+ p.expected("function name");
+ }
let args = p.span(arguments);
p.end_group();
- ExprCall { name, args }
+ Some(ExprCall {
+ callee: Box::new(name?.map(Expr::Ident)),
+ args,
+ })
}
/// Parse the body of a bracketed function call.
@@ -213,78 +218,66 @@ fn bracket_body(p: &mut Parser) -> Tree {
tree
}
-/// Parse a block expression: `{...}`.
-fn block(p: &mut Parser) -> Option<Expr> {
- p.start_group(Group::Brace, TokenMode::Code);
- let expr = p.span_if(expr);
- while !p.eof() {
- p.unexpected();
- }
- p.end_group();
- Some(Expr::Block(Box::new(expr?)))
-}
-
-/// Parse an expression: `term (+ term)*`.
-fn expr(p: &mut Parser) -> Option<Expr> {
- binops(p, term, |token| match token {
- Token::Plus => Some(BinOp::Add),
- Token::Hyph => Some(BinOp::Sub),
+/// Parse an identifier.
+fn ident(p: &mut Parser) -> Option<Ident> {
+ p.eat_map(|token| match token {
+ Token::Ident(id) => Some(Ident(id.into())),
_ => None,
})
}
-/// Parse a term: `factor (* factor)*`.
-fn term(p: &mut Parser) -> Option<Expr> {
- binops(p, factor, |token| match token {
- Token::Star => Some(BinOp::Mul),
- Token::Slash => Some(BinOp::Div),
- _ => None,
- })
+/// Parse an expression.
+fn expr(p: &mut Parser) -> Option<Expr> {
+ expr_with(p, 0)
}
-/// Parse binary operations of the from `a (<op> b)*`.
-fn binops(
- p: &mut Parser,
- operand: fn(&mut Parser) -> Option<Expr>,
- op: fn(Token) -> Option<BinOp>,
-) -> Option<Expr> {
- let mut lhs = p.span_if(operand)?;
-
- while let Some(op) = p.span_if(|p| p.eat_map(op)) {
- if let Some(rhs) = p.span_if(operand) {
- let span = lhs.span.join(rhs.span);
- let expr = Expr::Binary(ExprBinary {
- lhs: Box::new(lhs),
- op,
- rhs: Box::new(rhs),
- });
- lhs = expr.with_span(span);
- } else {
+/// Parse an expression with operators having at least the minimum precedence.
+fn expr_with(p: &mut Parser, min_prec: usize) -> Option<Expr> {
+ let mut lhs = match p.span_if(|p| p.eat_map(UnOp::from_token)) {
+ Some(op) => {
+ let prec = op.v.precedence();
+ let expr = p.span_if(|p| expr_with(p, prec))?;
+ let span = op.span.join(expr.span);
+ let unary = Expr::Unary(ExprUnary { op, expr: Box::new(expr) });
+ unary.with_span(span)
+ }
+ None => p.span_if(primary)?,
+ };
+
+ loop {
+ let op = match p.peek().and_then(BinOp::from_token) {
+ Some(binop) => binop,
+ None => break,
+ };
+
+ let mut prec = op.precedence();
+ if prec < min_prec {
break;
}
- }
- Some(lhs.v)
-}
+ match op.associativity() {
+ Associativity::Left => prec += 1,
+ Associativity::Right => {}
+ }
-/// Parse a factor of the form `-?value`.
-fn factor(p: &mut Parser) -> Option<Expr> {
- let op = |token| match token {
- Token::Plus => Some(UnOp::Pos),
- Token::Hyph => Some(UnOp::Neg),
- _ => None,
- };
+ let op = op.with_span(p.peek_span());
+ p.eat();
- if let Some(op) = p.span_if(|p| p.eat_map(op)) {
- p.span_if(factor)
- .map(|expr| Expr::Unary(ExprUnary { op, expr: Box::new(expr) }))
- } else {
- value(p)
+ let rhs = match p.span_if(|p| expr_with(p, prec)) {
+ Some(rhs) => Box::new(rhs),
+ None => break,
+ };
+
+ let span = lhs.span.join(rhs.span);
+ let binary = Expr::Binary(ExprBinary { lhs: Box::new(lhs), op, rhs });
+ lhs = binary.with_span(span);
}
+
+ Some(lhs.v)
}
-/// Parse a value.
-fn value(p: &mut Parser) -> Option<Expr> {
+/// Parse a primary expression.
+fn primary(p: &mut Parser) -> Option<Expr> {
let expr = match p.peek() {
// Template.
Some(Token::LeftBracket) => {
@@ -342,19 +335,25 @@ fn template(p: &mut Parser) -> Expr {
Expr::Template(tree)
}
+/// Parse a block expression: `{...}`.
+fn block(p: &mut Parser) -> Option<Expr> {
+ p.start_group(Group::Brace, TokenMode::Code);
+ let expr = p.span_if(expr);
+ while !p.eof() {
+ p.unexpected();
+ }
+ p.end_group();
+ Some(Expr::Block(Box::new(expr?)))
+}
+
/// Parse a parenthesized function call.
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Expr {
p.start_group(Group::Paren, TokenMode::Code);
let args = p.span(arguments);
p.end_group();
- Expr::Call(ExprCall { name, args })
-}
-
-/// Parse an identifier.
-fn ident(p: &mut Parser) -> Option<Ident> {
- p.eat_map(|token| match token {
- Token::Ident(id) => Some(Ident(id.into())),
- _ => None,
+ Expr::Call(ExprCall {
+ callee: Box::new(name.map(Expr::Ident)),
+ args,
})
}
@@ -388,14 +387,14 @@ fn stmt_let(p: &mut Parser) -> Option<Expr> {
if p.eat_if(Token::Eq) {
rhs = p.span_if(expr);
}
+
+ if !p.eof() {
+ p.expected_at("semicolon or line break", p.last_end());
+ }
} else {
p.expected("identifier");
}
- if !p.eof() {
- p.expected_at("semicolon or line break", p.last_end());
- }
-
p.end_group();
Some(Expr::Let(ExprLet { pat: pat?, expr: rhs.map(Box::new) }))
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index c1af2335..6be9d632 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -118,6 +118,23 @@ impl Pretty for ExprDict {
}
}
+/// A pair of a name and an expression: `pattern: dashed`.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Named {
+ /// The name: `pattern`.
+ pub name: Spanned<Ident>,
+ /// The right-hand side of the pair: `dashed`.
+ pub expr: Spanned<Expr>,
+}
+
+impl Pretty for Named {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str(&self.name.v);
+ p.push_str(": ");
+ self.expr.v.pretty(p);
+ }
+}
+
/// A template expression: `[*Hi* there!]`.
pub type ExprTemplate = Tree;
@@ -127,18 +144,246 @@ pub type ExprGroup = Box<Spanned<Expr>>;
/// A block expression: `{1 + 2}`.
pub type ExprBlock = Box<Spanned<Expr>>;
+/// A unary operation: `-x`.
+#[derive(Debug, Clone, PartialEq)]
+pub struct ExprUnary {
+ /// The operator: `-`.
+ pub op: Spanned<UnOp>,
+ /// The expression to operator on: `x`.
+ pub expr: Box<Spanned<Expr>>,
+}
+
+impl Pretty for ExprUnary {
+ fn pretty(&self, p: &mut Printer) {
+ self.op.v.pretty(p);
+ if self.op.v == UnOp::Not {
+ p.push_str(" ");
+ }
+ self.expr.v.pretty(p);
+ }
+}
+
+/// A unary operator.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum UnOp {
+ /// The plus operator: `+`.
+ Pos,
+ /// The negation operator: `-`.
+ Neg,
+ /// The boolean `not`.
+ Not,
+}
+
+impl UnOp {
+ /// Try to convert the token into a unary operation.
+ pub fn from_token(token: Token) -> Option<Self> {
+ Some(match token {
+ Token::Plus => Self::Pos,
+ Token::Hyph => Self::Neg,
+ Token::Not => Self::Not,
+ _ => return None,
+ })
+ }
+
+ /// The precedence of this operator.
+ pub fn precedence(self) -> usize {
+ match self {
+ Self::Pos | Self::Neg => 8,
+ Self::Not => 4,
+ }
+ }
+
+ /// The string representation of this operation.
+ pub fn as_str(self) -> &'static str {
+ match self {
+ Self::Pos => "+",
+ Self::Neg => "-",
+ Self::Not => "not",
+ }
+ }
+}
+
+impl Pretty for UnOp {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str(self.as_str());
+ }
+}
+
+/// A binary operation: `a + b`, `a / b`.
+#[derive(Debug, Clone, PartialEq)]
+pub struct ExprBinary {
+ /// The left-hand side of the operation: `a`.
+ pub lhs: Box<Spanned<Expr>>,
+ /// The operator: `+`.
+ pub op: Spanned<BinOp>,
+ /// The right-hand side of the operation: `b`.
+ pub rhs: Box<Spanned<Expr>>,
+}
+
+impl Pretty for ExprBinary {
+ fn pretty(&self, p: &mut Printer) {
+ self.lhs.v.pretty(p);
+ p.push_str(" ");
+ self.op.v.pretty(p);
+ p.push_str(" ");
+ self.rhs.v.pretty(p);
+ }
+}
+
+/// A binary operator.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum BinOp {
+ /// The addition operator: `+`.
+ Add,
+ /// The subtraction operator: `-`.
+ Sub,
+ /// The multiplication operator: `*`.
+ Mul,
+ /// The division operator: `/`.
+ Div,
+ /// The short-circuiting boolean `and`.
+ And,
+ /// The short-circuiting boolean `or`.
+ Or,
+ /// The equality operator: `==`.
+ Eq,
+ /// The inequality operator: `!=`.
+ Neq,
+ /// The less-than operator: `<`.
+ Lt,
+ /// The less-than or equal operator: `<=`.
+ Leq,
+ /// The greater-than operator: `>`.
+ Gt,
+ /// The greater-than or equal operator: `>=`.
+ Geq,
+ /// The assignment operator: `=`.
+ Assign,
+ /// The add-assign operator: `+=`.
+ AddAssign,
+ /// The subtract-assign oeprator: `-=`.
+ SubAssign,
+ /// The multiply-assign operator: `*=`.
+ MulAssign,
+ /// The divide-assign operator: `/=`.
+ DivAssign,
+}
+
+impl BinOp {
+ /// Try to convert the token into a binary operation.
+ pub fn from_token(token: Token) -> Option<Self> {
+ Some(match token {
+ Token::Plus => Self::Add,
+ Token::Hyph => Self::Sub,
+ Token::Star => Self::Mul,
+ Token::Slash => Self::Div,
+ Token::And => Self::And,
+ Token::Or => Self::Or,
+ Token::EqEq => Self::Eq,
+ Token::BangEq => Self::Neq,
+ Token::Lt => Self::Lt,
+ Token::LtEq => Self::Leq,
+ Token::Gt => Self::Gt,
+ Token::GtEq => Self::Geq,
+ Token::Eq => Self::Assign,
+ Token::PlusEq => Self::AddAssign,
+ Token::HyphEq => Self::SubAssign,
+ Token::StarEq => Self::MulAssign,
+ Token::SlashEq => Self::DivAssign,
+ _ => return None,
+ })
+ }
+
+ /// The precedence of this operator.
+ pub fn precedence(self) -> usize {
+ match self {
+ Self::Mul | Self::Div => 7,
+ Self::Add | Self::Sub => 6,
+ Self::Eq | Self::Neq | Self::Lt | Self::Leq | Self::Gt | Self::Geq => 5,
+ Self::And => 3,
+ Self::Or => 2,
+ Self::Assign
+ | Self::AddAssign
+ | Self::SubAssign
+ | Self::MulAssign
+ | Self::DivAssign => 1,
+ }
+ }
+
+ /// The associativity of this operator.
+ pub fn associativity(self) -> Associativity {
+ match self {
+ Self::Add
+ | Self::Sub
+ | Self::Mul
+ | Self::Div
+ | Self::And
+ | Self::Or
+ | Self::Eq
+ | Self::Neq
+ | Self::Lt
+ | Self::Leq
+ | Self::Gt
+ | Self::Geq => Associativity::Left,
+ Self::Assign
+ | Self::AddAssign
+ | Self::SubAssign
+ | Self::MulAssign
+ | Self::DivAssign => Associativity::Right,
+ }
+ }
+
+ /// The string representation of this operation.
+ pub fn as_str(self) -> &'static str {
+ match self {
+ Self::Add => "+",
+ Self::Sub => "-",
+ Self::Mul => "*",
+ Self::Div => "/",
+ Self::And => "and",
+ Self::Or => "or",
+ Self::Eq => "==",
+ Self::Neq => "!=",
+ Self::Lt => "<",
+ Self::Leq => "<=",
+ Self::Gt => ">",
+ Self::Geq => ">=",
+ Self::Assign => "=",
+ Self::AddAssign => "+=",
+ Self::SubAssign => "-=",
+ Self::MulAssign => "*=",
+ Self::DivAssign => "/=",
+ }
+ }
+}
+
+impl Pretty for BinOp {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str(self.as_str());
+ }
+}
+
+/// The associativity of a binary operator.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Associativity {
+ /// Left-associative: `a + b + c` is equivalent to `(a + b) + c`.
+ Left,
+ /// Right-associative: `a = b = c` is equivalent to `a = (b = c)`.
+ Right,
+}
+
/// An invocation of a function: `[foo ...]`, `foo(...)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprCall {
- /// The name of the function.
- pub name: Spanned<Ident>,
+ /// The callee of the function.
+ pub callee: Box<Spanned<Expr>>,
/// The arguments to the function.
pub args: Spanned<ExprArgs>,
}
impl Pretty for ExprCall {
fn pretty(&self, p: &mut Printer) {
- p.push_str(&self.name.v);
+ self.callee.v.pretty(p);
p.push_str("(");
self.args.v.pretty(p);
p.push_str(")");
@@ -154,7 +399,7 @@ pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) {
}
// Function name.
- p.push_str(&call.name.v);
+ call.callee.v.pretty(p);
// Find out whether this can be written with a body or as a chain.
//
@@ -216,102 +461,6 @@ impl Pretty for Argument {
}
}
-/// A pair of a name and an expression: `pattern: dashed`.
-#[derive(Debug, Clone, PartialEq)]
-pub struct Named {
- /// The name: `pattern`.
- pub name: Spanned<Ident>,
- /// The right-hand side of the pair: `dashed`.
- pub expr: Spanned<Expr>,
-}
-
-impl Pretty for Named {
- fn pretty(&self, p: &mut Printer) {
- p.push_str(&self.name.v);
- p.push_str(": ");
- self.expr.v.pretty(p);
- }
-}
-
-/// A unary operation: `-x`.
-#[derive(Debug, Clone, PartialEq)]
-pub struct ExprUnary {
- /// The operator: `-`.
- pub op: Spanned<UnOp>,
- /// The expression to operator on: `x`.
- pub expr: Box<Spanned<Expr>>,
-}
-
-impl Pretty for ExprUnary {
- fn pretty(&self, p: &mut Printer) {
- self.op.v.pretty(p);
- self.expr.v.pretty(p);
- }
-}
-
-/// A unary operator.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum UnOp {
- /// The plus operator: `+`.
- Pos,
- /// The negation operator: `-`.
- Neg,
-}
-
-impl Pretty for UnOp {
- fn pretty(&self, p: &mut Printer) {
- p.push_str(match self {
- Self::Pos => "+",
- Self::Neg => "-",
- });
- }
-}
-
-/// A binary operation: `a + b`, `a / b`.
-#[derive(Debug, Clone, PartialEq)]
-pub struct ExprBinary {
- /// The left-hand side of the operation: `a`.
- pub lhs: Box<Spanned<Expr>>,
- /// The operator: `+`.
- pub op: Spanned<BinOp>,
- /// The right-hand side of the operation: `b`.
- pub rhs: Box<Spanned<Expr>>,
-}
-
-impl Pretty for ExprBinary {
- fn pretty(&self, p: &mut Printer) {
- self.lhs.v.pretty(p);
- p.push_str(" ");
- self.op.v.pretty(p);
- p.push_str(" ");
- self.rhs.v.pretty(p);
- }
-}
-
-/// A binary operator.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum BinOp {
- /// The addition operator: `+`.
- Add,
- /// The subtraction operator: `-`.
- Sub,
- /// The multiplication operator: `*`.
- Mul,
- /// The division operator: `/`.
- Div,
-}
-
-impl Pretty for BinOp {
- fn pretty(&self, p: &mut Printer) {
- p.push_str(match self {
- Self::Add => "+",
- Self::Sub => "-",
- Self::Mul => "*",
- Self::Div => "/",
- });
- }
-}
-
/// A let expression: `let x = 1`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprLet {
diff --git a/src/syntax/span.rs b/src/syntax/span.rs
index fbde35af..21fa9ab8 100644
--- a/src/syntax/span.rs
+++ b/src/syntax/span.rs
@@ -1,10 +1,7 @@
+use std::cell::Cell;
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Range;
-#[cfg(test)]
-use std::cell::Cell;
-
-#[cfg(test)]
thread_local! {
static CMP_SPANS: Cell<bool> = Cell::new(true);
}
@@ -148,10 +145,27 @@ impl Span {
self.start.to_usize() .. self.end.to_usize()
}
+ /// Run some code with span comparisons disabled.
+ pub fn without_cmp<F, T>(f: F) -> T
+ where
+ F: FnOnce() -> T,
+ {
+ let prev = Self::cmp();
+ Self::set_cmp(false);
+ let val = f();
+ Self::set_cmp(prev);
+ val
+ }
+
+ /// Whether spans will currently be compared.
+ fn cmp() -> bool {
+ CMP_SPANS.with(Cell::get)
+ }
+
+ /// Whether spans should be compared.
+ ///
/// When set to `false` comparisons with `PartialEq` ignore spans.
- #[cfg(test)]
- #[allow(unused)]
- pub(crate) fn set_cmp(cmp: bool) {
+ fn set_cmp(cmp: bool) {
CMP_SPANS.with(|cell| cell.set(cmp));
}
}
@@ -169,12 +183,7 @@ impl Eq for Span {}
impl PartialEq for Span {
fn eq(&self, other: &Self) -> bool {
- #[cfg(test)]
- if !CMP_SPANS.with(Cell::get) {
- return true;
- }
-
- self.start == other.start && self.end == other.end
+ !Self::cmp() || (self.start == other.start && self.end == other.end)
}
}