diff --git a/src/diag.rs b/src/diag.rs index a7bea6f1b..fca905da2 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 d57ed1441..7b45c09a9 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; + let callee = self.v.callee.eval(ctx); - 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)); + 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); - 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")); + 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 54fe23247..c604f2d0a 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(&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(&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 0a273da56..939445f0a 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), - - (a, b) => { - ctx.diag(error!( - span, - "cannot divide {} by {}", - a.type_name(), - b.type_name() - )); - Value::Error - } + _ => 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(mut a: T, b: T) -> T where diff --git a/src/eval/scope.rs b/src/eval/scope.rs index a93de2695..ed4edbb96 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, value: impl Into) { + 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, value: impl Into) { - 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, value: impl Into) { + 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, value: impl Into) { - 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 20cc457c8..6fa702060 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 1a45c63c1..db28761bc 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 5638517bf..05990096c 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 e91ea6724..cea7e4e3c 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 5c4e774cf..ed83270d1 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 ee52c97c2..670104d68 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::(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 58fd91aee..95ca98470 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 { 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 622223fa3..00512c3f0 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 { 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 { 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 { 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 { - 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 identifier. +fn ident(p: &mut Parser) -> Option { + p.eat_map(|token| match token { + Token::Ident(id) => Some(Ident(id.into())), + _ => None, + }) } -/// Parse an expression: `term (+ term)*`. +/// Parse an expression. fn expr(p: &mut Parser) -> Option { - binops(p, term, |token| match token { - Token::Plus => Some(BinOp::Add), - Token::Hyph => Some(BinOp::Sub), - _ => None, - }) + expr_with(p, 0) } -/// Parse a term: `factor (* factor)*`. -fn term(p: &mut Parser) -> Option { - binops(p, factor, |token| match token { - Token::Star => Some(BinOp::Mul), - Token::Slash => Some(BinOp::Div), - _ => None, - }) -} +/// Parse an expression with operators having at least the minimum precedence. +fn expr_with(p: &mut Parser, min_prec: usize) -> Option { + 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)?, + }; -/// Parse binary operations of the from `a ( b)*`. -fn binops( - p: &mut Parser, - operand: fn(&mut Parser) -> Option, - op: fn(Token) -> Option, -) -> Option { - let mut lhs = p.span_if(operand)?; + loop { + let op = match p.peek().and_then(BinOp::from_token) { + Some(binop) => binop, + None => break, + }; - 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 { + let mut prec = op.precedence(); + if prec < min_prec { break; } + + match op.associativity() { + Associativity::Left => prec += 1, + Associativity::Right => {} + } + + let op = op.with_span(p.peek_span()); + p.eat(); + + 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 factor of the form `-?value`. -fn factor(p: &mut Parser) -> Option { - let op = |token| match token { - Token::Plus => Some(UnOp::Pos), - Token::Hyph => Some(UnOp::Neg), - _ => None, - }; - - 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) - } -} - -/// Parse a value. -fn value(p: &mut Parser) -> Option { +/// Parse a primary expression. +fn primary(p: &mut Parser) -> Option { 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 { + 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) -> 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 { - 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 { 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 c1af23354..6be9d632b 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, + /// The right-hand side of the pair: `dashed`. + pub expr: Spanned, +} + +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>; /// A block expression: `{1 + 2}`. pub type ExprBlock = Box>; +/// A unary operation: `-x`. +#[derive(Debug, Clone, PartialEq)] +pub struct ExprUnary { + /// The operator: `-`. + pub op: Spanned, + /// The expression to operator on: `x`. + pub expr: Box>, +} + +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 { + 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>, + /// The operator: `+`. + pub op: Spanned, + /// The right-hand side of the operation: `b`. + pub rhs: Box>, +} + +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 { + 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, + /// The callee of the function. + pub callee: Box>, /// The arguments to the function. pub args: Spanned, } 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, - /// The right-hand side of the pair: `dashed`. - pub expr: Spanned, -} - -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, - /// The expression to operator on: `x`. - pub expr: Box>, -} - -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>, - /// The operator: `+`. - pub op: Spanned, - /// The right-hand side of the operation: `b`. - pub rhs: Box>, -} - -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 fbde35afa..21fa9ab8c 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 = 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: 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) } } diff --git a/tests/lang/ref/bracket-call.png b/tests/lang/ref/bracket-call.png index 16afb1879..e89244842 100644 Binary files a/tests/lang/ref/bracket-call.png and b/tests/lang/ref/bracket-call.png differ diff --git a/tests/lang/ref/if.png b/tests/lang/ref/if.png index da99e185f..4c390c541 100644 Binary files a/tests/lang/ref/if.png and b/tests/lang/ref/if.png differ diff --git a/tests/lang/ref/let.png b/tests/lang/ref/let.png index 4b8a1131a..c555a9a07 100644 Binary files a/tests/lang/ref/let.png and b/tests/lang/ref/let.png differ diff --git a/tests/lang/typ/bracket-call.typ b/tests/lang/typ/bracket-call.typ index 4b92a4b8f..73250e08b 100644 --- a/tests/lang/typ/bracket-call.typ +++ b/tests/lang/typ/bracket-call.typ @@ -33,14 +33,20 @@ [f|f|f] // With body. -[f | box][💕] +// Error: 1:6-1:7 expected function name, found integer +[f | 1 | box][💕] -// Error: 1:2-1:2 expected function name -[|f true] +// Error: 2:2-2:2 expected function name +// Error: 1:3-1:3 expected function name +[||f true] // Error: 1:6-1:6 expected function name [f 1|] +// Error: 2:2-2:2 expected function name +// Error: 1:3-1:3 expected function name +[|][Nope] + // Error: 2:5-2:5 expected closing paren // Error: 1:8-1:9 expected expression, found closing paren [f (|f )] diff --git a/tests/lang/typ/expressions.typ b/tests/lang/typ/expressions.typ index 97aa8d812..49b8910db 100644 --- a/tests/lang/typ/expressions.typ +++ b/tests/lang/typ/expressions.typ @@ -3,44 +3,118 @@ #let a = 2 #let b = 4 +// Error: 1:14-1:17 cannot apply '+' to string +#let error = +"" + // Paren call. -[eq f(1), "f(1)"] -[eq type(1), "integer"] +[test f(1), "f(1)"] +[test type(1), "integer"] // Unary operations. -[eq +1, 1] -[eq -1, 1-2] -[eq --1, 1] +[test +1, 1] +[test -1, 1-2] +[test --1, 1] -// Binary operations. -[eq "a" + "b", "ab"] -[eq 1-4, 3*-1] -[eq a * b, 8] -[eq 12pt/.4, 30pt] +// Math operations. +[test "a" + "b", "ab"] +[test 1-4, 3*-1] +[test a * b, 8] +[test 12pt/.4, 30pt] +[test 1e+2-1e-2, 99.99] // Associativity. -[eq 1+2+3, 6] -[eq 1/2*3, 1.5] +[test 1+2+3, 6] +[test 1/2*3, 1.5] // Precedence. -[eq 1+2*-3, -5] +[test 1+2*-3, -5] + +// Short-circuiting logical operators. +[test not "a" == "b", true] +[test not 7 < 4 and 10 == 10, true] +[test 3 < 2 or 4 < 5, true] +[test false and false or true, true] + +// Right-hand side not even evaluated. +[test false and dont-care, false] +[test true or dont-care, true] + +// Equality and inequality. +[test "ab" == "a" + "b", true] +[test [*Hi*] == [*Hi*], true] +[test "a" != "a", false] +[test [*] != [_], true] +[test (1, 2, 3) == (1, 2) + (3,), true] +[test () == (1,), false] +[test (a: 1, b: 2) == (b: 2, a: 1), true] +[test (:) == (a: 1), false] +[test 1 == "hi", false] +[test 1 == 1.0, true] +[test 30% == 30% + 0cm, true] +[test 1in == 0% + 72pt, true] +[test 30% == 30% + 1cm, false] + +// Comparisons. +[test 13 * 3 < 14 * 4, true] +[test 5 < 10, true] +[test 5 > 5, false] +[test 5 <= 5, true] +[test 5 <= 4, false] +[test 45deg < 1rad, true] + +// Assignment. +#let x = "some" +#let y = "some" +[test (x = y = "") == none and x == none and y == "", true] + +// Modify-assign operators. +#let x = 0 +{ x = 10 } [test x, 10] +{ x -= 5 } [test x, 5] +{ x += 1 } [test x, 6] +{ x *= x } [test x, 36] +{ x /= 2.0 } [test x, 18.0] +{ x = "some" } [test x, "some"] +{ x += "thing" } [test x, "something"] + +// Error: 1:3-1:4 unknown variable +{ z = 1 } + +// Error: 1:3-1:6 cannot assign to this expression +{ (x) = "" } + +// Error: 1:3-1:8 cannot assign to this expression +{ 1 + 2 = 3} + +// Error: 1:3-1:6 cannot assign to constant +{ box = "hi" } + +// Works if we define box before (since then it doesn't resolve to the standard +// library version anymore). +#let box = ""; { box = "hi" } // Parentheses. -[eq (a), 2] -[eq (2), 2] -[eq (1+2)*3, 9] - -// Error: 1:3-1:10 cannot add integer and string -{(1 + "2")} - -// Confusion with floating-point literal. -[eq 1e+2-1e-2, 99.99] +[test (a), 2] +[test (2), 2] +[test (1+2)*3, 9] // Error: 1:3-1:3 expected expression {-} -// Error: 1:8-1:8 expected expression -[eq {1+}, 1] +// Error: 1:10-1:10 expected expression +[test {1+}, 1] -// Error: 1:8-1:8 expected expression -[eq {2*}, 2] +// Error: 1:10-1:10 expected expression +[test {2*}, 2] + +// Error: 1:7-1:16 cannot apply '-' to boolean +[test -not true, error] + +// Error: 1:2-1:8 cannot apply 'not' to array +{not ()} + +// Error: 1:3-1:10 cannot apply '+' to integer and string +{(1 + "2")} + +// Error: 1:2-1:12 cannot apply '<=' to relative and relative +{30% <= 40%} diff --git a/tests/lang/typ/let.typ b/tests/lang/typ/let.typ index 3f8f5e0fa..7e9246bb9 100644 --- a/tests/lang/typ/let.typ +++ b/tests/lang/typ/let.typ @@ -1,10 +1,10 @@ // Automatically initialized with `none`. #let x -[eq x, none] +[test x, none] // Initialized with `1`. #let y = 1 -[eq y, 1] +[test y, 1] // Initialize with template, not terminated by semicolon in template. #let v = [Hello; there] @@ -15,15 +15,19 @@ 2, 3, ) -[eq x, (1, 2, 3)] +[test x, (1, 2, 3)] // Multiple bindings in one line. -#let x = "a"; #let y = "b"; [eq x + y, "ab"] +#let x = "a"; #let y = "b"; [test x + y, "ab"] // Invalid name. // Error: 1:6-1:7 expected identifier, found integer #let 1 +// Invalid name. +// Error: 1:6-1:7 expected identifier, found integer +#let 1 = 2 + // Terminated by end of line before binding name. // Error: 1:5-1:5 expected identifier #let @@ -35,24 +39,24 @@ The Fi#let;rst // Terminated with just a line break. #let v = "a" -The Second [eq v, "a"] +The Second [test v, "a"] // Terminated with semicolon + line break. #let v = "a"; -The Third [eq v, "a"] +The Third [test v, "a"] // Terminated with just a semicolon. -The#let v = "a"; Fourth [eq v, "a"] +The#let v = "a"; Fourth [test v, "a"] // Terminated by semicolon even though we are in a paren group. // Error: 2:25-2:25 expected expression // Error: 1:25-1:25 expected closing paren -The#let array = (1, 2 + ;Fifth [eq array, (1, 2)] +The#let array = (1, 2 + ;Fifth [test array, (1, 2)] // Not terminated. // Error: 1:16-1:16 expected semicolon or line break -The#let v = "a"Sixth [eq v, "a"] +The#let v = "a"Sixth [test v, "a"] // Not terminated. // Error: 1:16-1:16 expected semicolon or line break -The#let v = "a" [eq v, "a"] Seventh +The#let v = "a" [test v, "a"] Seventh diff --git a/tests/typeset.rs b/tests/typeset.rs index fd5f4c815..dbcb65173 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -142,10 +142,12 @@ fn test( let mut ok = true; let mut frames = vec![]; + let mut lines = 0; for (i, part) in src.split("---").enumerate() { - let (part_ok, part_frames) = test_part(i, part, env); + let (part_ok, part_frames) = test_part(part, i, lines, env); ok &= part_ok; frames.extend(part_frames); + lines += part.lines().count() as u32; } if !frames.is_empty() { @@ -177,7 +179,7 @@ fn test( ok } -fn test_part(i: usize, src: &str, env: &mut Env) -> (bool, Vec) { +fn test_part(src: &str, i: usize, lines: u32, env: &mut Env) -> (bool, Vec) { let map = LineMap::new(src); let (compare_ref, ref_diags) = parse_metadata(src, &map); @@ -215,14 +217,14 @@ fn test_part(i: usize, src: &str, env: &mut Env) -> (bool, Vec) { for diag in &diags { if !ref_diags.contains(diag) { print!(" Not annotated | "); - print_diag(diag, &map); + print_diag(diag, &map, lines); } } for diag in &ref_diags { if !diags.contains(diag) { print!(" Not emitted | "); - print_diag(diag, &map); + print_diag(diag, &map, lines); } } } @@ -288,7 +290,7 @@ fn register_helpers(scope: &mut Scope, panicked: Rc>) { Value::Str(p.finish()) } - let eq = move |ctx: &mut EvalContext, args: &mut Args| -> Value { + let test = move |ctx: &mut EvalContext, args: &mut Args| -> Value { let lhs = args.require::(ctx, "left-hand side"); let rhs = args.require::(ctx, "right-hand side"); if lhs != rhs { @@ -302,13 +304,15 @@ fn register_helpers(scope: &mut Scope, panicked: Rc>) { } }; - scope.set("f", ValueFunc::new("f", f)); - scope.set("eq", ValueFunc::new("eq", eq)); + scope.define("f", ValueFunc::new("f", f)); + scope.define("test", ValueFunc::new("test", test)); } -fn print_diag(diag: &Spanned, map: &LineMap) { - let start = map.location(diag.span.start).unwrap(); - let end = map.location(diag.span.end).unwrap(); +fn print_diag(diag: &Spanned, map: &LineMap, lines: u32) { + let mut start = map.location(diag.span.start).unwrap(); + let mut end = map.location(diag.span.end).unwrap(); + start.line += lines; + end.line += lines; println!("{}: {}-{}: {}", diag.v.level, start, end, diag.v.message); }