From ac788f2082711161ec8208eede04d9a2bae02241 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 22 Jan 2021 17:16:42 +0100 Subject: [PATCH] =?UTF-8?q?Many=20more=20expressions=20=F0=9F=A5=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boolean, equality, comparison and assignment expression parsing and evaluation. --- src/diag.rs | 6 - src/eval/call.rs | 32 ++- src/eval/mod.rs | 134 +++++++++--- src/eval/ops.rs | 179 +++++++++------- src/eval/scope.rs | 33 ++- src/eval/value.rs | 12 -- src/geom/length.rs | 5 + src/geom/linear.rs | 6 +- src/geom/relative.rs | 7 +- src/library/mod.rs | 4 +- src/library/style.rs | 2 +- src/parse/collection.rs | 4 +- src/parse/mod.rs | 179 ++++++++-------- src/syntax/expr.rs | 349 +++++++++++++++++++++++--------- src/syntax/span.rs | 35 ++-- tests/lang/ref/bracket-call.png | Bin 22257 -> 22101 bytes tests/lang/ref/if.png | Bin 4352 -> 3970 bytes tests/lang/ref/let.png | Bin 3347 -> 3542 bytes tests/lang/typ/bracket-call.typ | 12 +- tests/lang/typ/expressions.typ | 126 +++++++++--- tests/lang/typ/let.typ | 24 ++- tests/typeset.rs | 24 ++- 22 files changed, 766 insertions(+), 407 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; + 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 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(&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 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), - - (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 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, 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 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::(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 { 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 { 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 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, + /// 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 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 = 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 16afb1879d44e9ae6092fdcd5c7aa651f3fd7a60..e892448424f667f759e88fd4c71e5ec64f97f14e 100644 GIT binary patch literal 22101 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%0A}JG{RxbSht*J8Wtjh?XHL@qZsXX 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 C)MKdt literal 22257 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 diff --git a/tests/lang/ref/let.png b/tests/lang/ref/let.png index 4b8a1131a7fe1d30a419d2aac0fefa84328a8786..c555a9a07c25f2c72c8386bc880940dfc45a09e9 100644 GIT binary patch literal 3542 zcma)`^RS(nZ)F49Yn*7eczI$%-CioyCWf42lWs_)=W&3PB@Gq4msguDcM5y zAwps&2Ku7`$`_;1e)Rvq8)0n2Cf271?>^VzKE-HeT?Xky!;IK#S*O4&=-_BwD;-x| zimugZIgI4DJxq_bse0lmQ1hSr3GOQQiB(4Sq+}u6M+irkjRrh<}du0{g z%9Q&43xE?AqV0Y5I*uYq$oyQbmEtOQwTT*}+0|Ry-oS1>-_DbFC1~nggg-FqQ5H{4 zlAyel;}tTkoVL3B{a_h-kRsLzrvB+Bxj3LWqhBi+6Rix}e$y1{}VCyvyTLTT#$9kk|bzuQqM9-6~wY>CS z^*M4&m+#cqAUk$^$FFB@rgJOkjn78H8BWeT;Vojr_s$!;lEpLP4*S-!BXZb^3}+<- zMYhiGB~n{p&vg&mhO7;L`W|yGh}wFH=%YdR$y!)YmbKWKmaeDK z-{F`OZJOtc8FTNj>UYnSona~xOz~E4!C`A|1=)D>3iuIVaE0mO@IjBIwPFPQ^z3>* z#Nt*wdZ>+}A@xU$(KVKxcLPuQL)Z_M46Y?q)9;QsCJ>b-BB2`VFDujqHHbn*d?hWe z*>dr74N!B010tIrMO&1loT7GAN-VNyu<~qa2B&t5TPB=(tPoU^UzCn9@q>{lNe)9= ziLtH>p+Spb4f8jI@FDb27)0Fi8C$&zyD_EFFWg60x^s^p%rVl9=_eD_m&I5zoWS>u zAa;|GXOU1;-CHr9mU*+LsNnhsRz=KNKTL`O!qF@ z|E3A4JV7reAnB^x0CxGJTE{GW&t&xM1H4dsO0Rq ztu#-{uVnzt<^GB>T)`W|(a^J=%FwESTVE_zy+xht1rI)Yf^8(4f|F?jFpP2z-ygv#3rf|n}zJSX@PkDB_zY?M67?LozURwhV zsU$R*LOc>MZV9UAk#G;Q7`+WegXQTNV03Wlb#r5?)qb)3$P5548`7c932%to7pxYv z6Y8C}OvY%)eY_)!S_tS%0Q;N`OMZGKse>%cpP>WV37>`DUHBHANRT^|p0-q=n}Y~T zeYmxineK_ttlpqWU*Dcd`lw})QoH2J`4zQb9*V|%4R^}EHlv77k>}|>NV>Bb#Daf- zE9JWS>SX3!tyDJDAdm1b(S?m4KXKr8S7H>Y{KP-$PAKJ_qHPDOWm zo&v);Y8`_Mwsuhu{rsWr6?VX%Z8)2_=qZGeZA=3Hyg=?bKPC1aLJ{}wMQJ#tI4HN+ zoq`!swsABXnjRTt=DEcCoFLvYfgxxa3`;~dnxSap0`yw$3W9*SS9o^fjuSC|_<|^+qEhm&H zoRXs<;r%w!j6H5K{1mm_fx_lRy`;eVi&9lpD1Obrm@tX)az&znv)tFEHvpdNhl@)`k+-WehQo_6` zpUku(DaNG`Q09Hyt{@@`$z4NwSJg=q2w#92XoA>pd-Suv2Q7>@T1WAboir6{!WI5R z6&}9%nyO%5wouY@x?(U_%H#oV$ZvnFB1Yri))9tCP`2+h4FCYKe~>mD^O zgr*D5tnyb0lG(cx|FDL$X5nGI>&({r_9nm&HU}daW_N0EZr+YCgS-?#2ObX`Mauiy zc-@{h)PeqkZJZh{^{p&E{*@+YCmWJ(d*PhV;*hCIZi-1tBo_S-xDEaWw3F)Ou^biXZvp`bgSz<_Fz*aRQVz)`*K#)`2Dt7P@^^0-W&S8H#ba$#3_9>JTE? zvyahtEI|Wz#Um){Q&xF3P@jodT@C{aJEO>9Wi`$N0oip>w^PirfLnMe=j^QU$23u- z5QhTkEx4|3O|i9E$?%{MkT_hIVV8%3mkM%5Fo6Asz{&LYY#7A2ovbB^9b0};T_4oW zOvM!lxEcRG2aiu9IB8vyVd0v%rIViglD&S#*4`Z~1-jG}WQNl8fdTbS z+`V-RK`OCG)jUzdRWDR|ZXCF&I%fPJi#%_YVPe5WeaPn^zS7mIaWUDWNk^cEAmhe4 zVHEKZcB(F(k-6f=u(~R

HmEO9gAcZpTfFT1vG@&RS9qaVx znJD)n?pbHpLuo8$f1+#9A^t(uBa9kGt%jZt(oufjXtz*wMa?_B_aF~`W$5#Phg16g zH8NW5xOZf1w#O1`wu<$dImy4l7EB#C+1-g)KI5fr?}*CK4^*36e^)C=)_r_EJfc{o zYX+=Rs1Cys!b`Q z5Y4@xJpL{Nt#Oz}2|4owNxKE6>PpkeZblL-dB;p^ZatxRCKcYtV`r79`UbbB8HH2J^OA0B||b3xY+CJSZmsCBy}?eTEX0?azz=$t-ahD1h?3 zv6PKO!geyMZ9;0Oe|JfmP5=5W1Rb$SyDcj8#|<8OYH00-lH#c8HG1;|&gTCJ$^Yc% hcX?9m|5V3?6W09YrOTm%V8-7I8{XK$sMNrf^lxf%Tjl@& literal 3347 zcma)>eu=G_50^{e!s`#_5S1Y{_p*HzaH=B@^!fV;`EIWXvH87rWk>&`ev8n0MOeq=J&k;HES!v&k=M z>#4iq0GHr@u?DA(W=t#zEN8#7{yF~WU+m^pZXiBItp#S zQHn|EuuYPwJbI$X&pl+rOC>o1EXM1M-EX{8EH!Ylcxm9Ds@{0o+b5&xBQ}xo!kqc^ zrOlo33SMbfkkrV=BxdLA&?xAQ@^|v~i2400-lADmQh5cC{oj?Wl#(lRTR@kd?EX@G zIROnzr5o-0P6X==C^3Adm#Fn24}fVj-`RxknJCFd^BEhCL#0a6{jjkO?jmS+nv4wL zl{)?Pya;v_&bHO(F9sy$g{xK*kqRQS#Z8&D^5pO#pC!BE{CWMIf)9V|yy| zP<03QcMW$PCJdNQ1Z+PB4q@fdm8uHaCI->BC{khPnTOH7YAnBXvl{;Rv%RBMINE7^ zj3&*o$({<6`Mp_ft`D{tOysnmQ{g7ws@14*I9}i+6^60XWPCg!ZEE&w3u&UIky4Xp zs&a#Fe$=Wg96G-xb$Ay(A9o6@*I>8%lSpy=n@pQAh+yx4DsCHbkWE)UiyZ1r09K$} zXkcV~%NUP`MWU7DjyZ0hwrbKIhakBA&Oh~&4H6GoUNT=Zwd*@h^Do))rImsr@0@|y z8udoe*wM>;`tsz->#CJJ-6MbNt3BCtX4&^)$={d<`?I^4w?{H!e;NIFzizy!0TT3e zWF;*JYQVzAw)dg@##_N?xnI5n3Q$sv#*CfHWxE=b6Dm8b1w;i0(rS`UvO+>RLnw zHa&>kOMqp~iN}EE9>i3wURJ&M zJu+It0c9-b@Is$FmugtI#!S?i;VNj+D)krZQmmff!0xAJ;9JYhEO z2#kjK^=d73J*P;dH?V^6i^4hWas<2n5x{5Mh-gefd5I6pFtM7hA~pT}d_`?9+Lbab z?j!IzZVh5zX2}Ha)+XDlbo`HvlfZ)%m0fq!7ei}Rv!+n1LidPK;?Bi3r}mXt4`^_~ zpyeQ@`Wt29;PGM;T98OTMldw{|^ma;^So;kg8yTfzk8 ztAJ)T!y0rugS$I4c5fu~+RUK$9UPEII;Yo!r)|8sZ}#a{Kb?o>5Q-InHR3h@yyK#kwT&9Ir)wq!eT*0|OxFGwUcfUv)EGf(yKpse~%hghV&T`$>lzc0y z5yaOv22)<3{cOGt2Z_bml~zq=B+4LseV+o3MAcJ7m1DU?K^BsVQp&PNi*VAq>b9iX zzNz3W1q;u&rgsGj;3!@2^i6|gIkqyp)Rz~HXO64t(# znHiHO)o@||?HNU7)!&xWN;>{foLex14{iSApYvajiXirY+N>{^p%n*yoI5DUbFhUh zw+C&^$TW#>V|J0a+%hC#^K-&el#B2D(_!qm+8o$#IySf!FjnGME&G;j2({mQ){;xG zemGQuy=7*~y{rBt&Q&k+j?Lu_m}+-Bm)yhb-bucAY$djJUY$9DFMR6Px~bNYSnFGQ zp4-6o{&vNeoPZTb1RI5P>;CxPZboPpG5%2kI93--G2p=@CQe&k9$xRvw8c|?3={{8 zDq8FTbaS==-=3MrCn4wyhTS%9E;+2|a;dY@JqY{<=2h0W88v#cIGZx~`?j{Hmc3*X$1yTPp_<=}a@aqd4VKwA2*!zy4o@W+Y`N);Bt~wHU#C@-qUw6Meo4VVN!@6;p!N>o6Pt1v#L`sk< z%qxQ@BI@7ON>{8`rXd;UV>Pwzn!IdRevkl$_P^)2DTQU3;BMT@Y1e5DQP|415NoE2 z-?UMQXrCb6Ik-H@r1*e5Gl-VLKfKRd}~M${@I z{XtLfP94x1(mh>z2#58d?q2>z0LKjy%n@ul3oOJ~^?W)($)koc8b*sWF;+en8@%K{ z-T{tgAR$Exe+X1lg)|N2?h2hU1z}>}rhtU-zae(EpjG;#tILdFCzscjba3YUJ8_La zq^^bV$m4+?WAdELC2R%^)R%=aoPaJfo5R6tj4180sMYg;ieE}naY7-gC}cy0$x;tH zH!lV(*Z9Q|a~5v_vR{qsn=!21hJsQ=OT_I>`l!xJoxV{b%2%PAuU)>|0UY{)?;`g4 ze#brq!yfUbjCGr#IrnVhss@ge54xutxBM9R4}$xjGUjT$vzo^dWnQLF@8W;aHe<0} z3Nbrg>G>D*nobt@r+O@3e%6p`(R6$;_h&14{4b6dEY^|GDlBT$o*SJ+tWVJdL`0p? zKNWR)94@bx88cFiizobmI!U$nMXNRq+1S~Cx;0f4_97?O+kh8SA*FtnpDnTv^Y22(Vti#Kz%f5*Sz^pr$cp_bKX z;ulShruH*mWy3V74&osF$Y{6qXjzhz(}*zBBJRwJ+Yi>iPPWjikBa(Ok{A|a*IwXa zu4`aLPGFrg4s`)zo&USG<$+{_w6S*>9Zt{^*1&uG*Xw_V&dcQlVYhvRigpHa4@t~R z{ky^Hi-OMpag8#ZQ~SYgXT2GiQkI@e)VijWC~G(|91fYE?7LmKI&V46o!ZZ7B3RI& zNnQs&pfX?GuvMnPp%r=lWmSbTCNzirgnQN9GUJ|Mkyi>_gl>WhprRmLg*6f4%3t{c zNh{BVqkra8FVW@{rVMUlKQgT0e9zMZeNn8{;Hp@}(ZLRxzPHs_7n2};^E0W|2z&{= zvV|&Kx2CfzjQJbaGCUk!)g&g71M3+0JI5A%pMSp6OKx0{T^v~P_kD`@KTnU{R4Zn1 zqi*DJy92m~3LF=>n0^VdLEb#FDHb}*leqqMY8z~Osx-a;HN?;>c=-EOcsFD&HH+~C zA29F)E0=CM=`C#-(tX8)Vr$#4UZU9#d8B*bFE(IGo@?|>K(UlU{Yf4G-=7ljjA`lJ z@W)T6^L226sX7%Ni5FH%!TX-GzA9vE{Nu36BMs&Y*BCE5+SR-ndk^q$EHx_Zt?|A4 zN!s#hONk+o+m|SzaJ}LHi-NR?Ai1KewUZEE+mqh+5GmD!-m(=(TH0IjVuy-*a|EAA z4WFI8#QGG2?r<#8_2Da#8j639=<5hbz(=38Z;ykl|CbZ~GD*7m&5D2LkADY|iHVhD Y+xjN?6O(Vh|CM&^uueAhRz9i!0@;KZ8UO$Q diff --git a/tests/lang/typ/bracket-call.typ b/tests/lang/typ/bracket-call.typ index 4b92a4b8..73250e08 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 97aa8d81..49b8910d 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 3f8f5e0f..7e9246bb 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 fd5f4c81..dbcb6517 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); }