Many more expressions 🥗

Boolean, equality, comparison and assignment expression parsing and evaluation.
This commit is contained in:
Laurenz 2021-01-22 17:16:42 +01:00
parent 0de4f3ed7b
commit ac788f2082
22 changed files with 766 additions and 407 deletions

View File

@ -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.

View File

@ -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
}
}

View File

@ -164,13 +164,13 @@ impl Eval for Spanned<&Expr> {
Value::Error
}
},
Expr::Bool(v) => Value::Bool(*v),
Expr::Int(v) => Value::Int(*v),
Expr::Float(v) => Value::Float(*v),
Expr::Length(v, unit) => Value::Length(Length::with_unit(*v, *unit)),
Expr::Angle(v, unit) => Value::Angle(Angle::with_unit(*v, *unit)),
Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Expr::Color(v) => Value::Color(Color::Rgba(*v)),
&Expr::Bool(v) => Value::Bool(v),
&Expr::Int(v) => Value::Int(v),
&Expr::Float(v) => Value::Float(v),
&Expr::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
&Expr::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
&Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
&Expr::Color(v) => Value::Color(Color::Rgba(v)),
Expr::Str(v) => Value::Str(v.clone()),
Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)),
Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)),
@ -210,16 +210,27 @@ impl Eval for Spanned<&ExprUnary> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let value = self.v.expr.as_ref().eval(ctx);
if let Value::Error = value {
if value == Value::Error {
return Value::Error;
}
let span = self.v.op.span.join(self.v.expr.span);
match self.v.op.v {
UnOp::Pos => ops::pos(ctx, span, value),
UnOp::Neg => ops::neg(ctx, span, value),
let ty = value.type_name();
let out = match self.v.op.v {
UnOp::Pos => ops::pos(value),
UnOp::Neg => ops::neg(value),
UnOp::Not => ops::not(value),
};
if out == Value::Error {
ctx.diag(error!(
self.span,
"cannot apply '{}' to {}",
self.v.op.v.as_str(),
ty,
));
}
out
}
}
@ -227,20 +238,90 @@ impl Eval for Spanned<&ExprBinary> {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let lhs = self.v.lhs.as_ref().eval(ctx);
let rhs = self.v.rhs.as_ref().eval(ctx);
match self.v.op.v {
BinOp::Add => self.apply(ctx, ops::add),
BinOp::Sub => self.apply(ctx, ops::sub),
BinOp::Mul => self.apply(ctx, ops::mul),
BinOp::Div => self.apply(ctx, ops::div),
BinOp::And => self.apply(ctx, ops::and),
BinOp::Or => self.apply(ctx, ops::or),
BinOp::Eq => self.apply(ctx, ops::eq),
BinOp::Neq => self.apply(ctx, ops::neq),
BinOp::Lt => self.apply(ctx, ops::lt),
BinOp::Leq => self.apply(ctx, ops::leq),
BinOp::Gt => self.apply(ctx, ops::gt),
BinOp::Geq => self.apply(ctx, ops::geq),
BinOp::Assign => self.assign(ctx, |_, b| b),
BinOp::AddAssign => self.assign(ctx, ops::add),
BinOp::SubAssign => self.assign(ctx, ops::sub),
BinOp::MulAssign => self.assign(ctx, ops::mul),
BinOp::DivAssign => self.assign(ctx, ops::div),
}
}
}
impl Spanned<&ExprBinary> {
/// Apply a basic binary operation.
fn apply<F>(&self, ctx: &mut EvalContext, op: F) -> Value
where
F: FnOnce(Value, Value) -> Value,
{
let lhs = self.v.lhs.eval(ctx);
// Short-circuit boolean operations.
match (self.v.op.v, &lhs) {
(BinOp::And, Value::Bool(false)) => return Value::Bool(false),
(BinOp::Or, Value::Bool(true)) => return Value::Bool(true),
_ => {}
}
let rhs = self.v.rhs.eval(ctx);
if lhs == Value::Error || rhs == Value::Error {
return Value::Error;
}
let span = self.v.lhs.span.join(self.v.rhs.span);
match self.v.op.v {
BinOp::Add => ops::add(ctx, span, lhs, rhs),
BinOp::Sub => ops::sub(ctx, span, lhs, rhs),
BinOp::Mul => ops::mul(ctx, span, lhs, rhs),
BinOp::Div => ops::div(ctx, span, lhs, rhs),
let lhty = lhs.type_name();
let rhty = rhs.type_name();
let out = op(lhs, rhs);
if out == Value::Error {
ctx.diag(error!(
self.span,
"cannot apply '{}' to {} and {}",
self.v.op.v.as_str(),
lhty,
rhty,
));
}
out
}
/// Apply an assignment operation.
fn assign<F>(&self, ctx: &mut EvalContext, op: F) -> Value
where
F: FnOnce(Value, Value) -> Value,
{
let rhs = self.v.rhs.eval(ctx);
let span = self.v.lhs.span;
if let Expr::Ident(id) = &self.v.lhs.v {
if let Some(slot) = ctx.scopes.get_mut(id) {
let lhs = std::mem::replace(slot, Value::None);
*slot = op(lhs, rhs);
return Value::None;
} else {
if ctx.scopes.is_const(id) {
ctx.diag(error!(span, "cannot assign to constant"));
} else {
ctx.diag(error!(span, "unknown variable"));
}
}
} else {
ctx.diag(error!(span, "cannot assign to this expression"));
}
Value::Error
}
}
@ -263,20 +344,21 @@ impl Eval for Spanned<&ExprIf> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let condition = self.v.condition.eval(ctx);
if let Value::Bool(boolean) = condition {
if boolean {
return if boolean {
self.v.if_body.eval(ctx)
} else if let Some(expr) = &self.v.else_body {
expr.eval(ctx)
} else {
Value::None
}
} else {
};
} else if condition != Value::Error {
ctx.diag(error!(
self.v.condition.span,
"expected boolean, found {}",
condition.type_name()
condition.type_name(),
));
Value::Error
}
Value::Error
}
}

View File

@ -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<T, A>(mut a: T, b: T) -> T
where

View File

@ -21,7 +21,12 @@ impl<'a> Scopes<'a> {
Self { top: Scope::new(), scopes: vec![], base }
}
/// Look up the value of a variable in the scopes.
/// Define a variable in the active scope.
pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) {
self.top.define(var, value);
}
/// Look up the value of a variable.
pub fn get(&self, var: &str) -> Option<&Value> {
iter::once(&self.top)
.chain(&self.scopes)
@ -29,9 +34,18 @@ impl<'a> Scopes<'a> {
.find_map(|scope| scope.get(var))
}
/// Define a variable in the active scope.
pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) {
self.top.set(var, value);
/// Get a mutable reference to a variable.
pub fn get_mut(&mut self, var: &str) -> Option<&mut Value> {
iter::once(&mut self.top)
.chain(&mut self.scopes)
.find_map(|scope| scope.get_mut(var))
}
/// Return whether the variable is constant (not writable).
///
/// Defaults to `false` if the variable does not exist.
pub fn is_const(&self, var: &str) -> bool {
self.base.get(var).is_some()
}
}
@ -47,14 +61,19 @@ impl Scope {
Self::default()
}
/// Define a new variable.
pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) {
self.values.insert(var.into(), value.into());
}
/// Look up the value of a variable.
pub fn get(&self, var: &str) -> Option<&Value> {
self.values.get(var)
}
/// Store the value for a variable.
pub fn set(&mut self, var: impl Into<String>, value: impl Into<Value>) {
self.values.insert(var.into(), value.into());
/// Get a mutable reference to a variable.
pub fn get_mut(&mut self, var: &str) -> Option<&mut Value> {
self.values.get_mut(var)
}
}

View File

@ -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 {

View File

@ -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()

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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))
};
}

View File

@ -58,7 +58,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value {
let snapshot = ctx.state.clone();
if let Some(linear) = args.find::<Linear>(ctx) {
if linear.is_absolute() {
if linear.rel.is_zero() {
ctx.state.font.size = linear.abs;
ctx.state.font.scale = Relative::ONE.into();
} else {

View File

@ -1,5 +1,4 @@
use super::*;
use crate::diag::Deco;
/// Parse the arguments to a function call.
pub fn arguments(p: &mut Parser) -> ExprArgs {
@ -54,9 +53,8 @@ fn argument(p: &mut Parser) -> Option<Argument> {
let first = p.span_if(expr)?;
if p.eat_if(Token::Colon) {
if let Expr::Ident(ident) = first.v {
let expr = p.span_if(expr)?;
let name = ident.with_span(first.span);
p.deco(Deco::Name.with_span(name.span));
let expr = p.span_if(expr)?;
Some(Argument::Named(Named { name, expr }))
} else {
p.diag(error!(first.span, "expected identifier"));

View File

@ -49,7 +49,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
let node = match p.peek()? {
// Bracket call.
Token::LeftBracket => {
return Some(Node::Expr(bracket_call(p)));
return Some(Node::Expr(bracket_call(p)?));
}
// Code block.
@ -153,22 +153,30 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String {
}
/// Parse a bracketed function call.
fn bracket_call(p: &mut Parser) -> Expr {
fn bracket_call(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Bracket, TokenMode::Code);
// One header is guaranteed, but there may be more (through chaining).
let mut outer = vec![];
let mut inner = p.span(bracket_subheader);
let mut inner = p.span_if(bracket_subheader);
while p.eat_if(Token::Pipe) {
outer.push(inner);
inner = p.span(bracket_subheader);
if let Some(new) = p.span_if(bracket_subheader) {
outer.extend(inner);
inner = Some(new);
}
}
p.end_group();
if p.peek() == Some(Token::LeftBracket) {
let body = p.span(|p| Expr::Template(bracket_body(p)));
let body = if p.peek() == Some(Token::LeftBracket) {
Some(p.span(|p| Expr::Template(bracket_body(p))))
} else {
None
};
let mut inner = inner?;
if let Some(body) = body {
inner.span.expand(body.span);
inner.v.args.v.push(Argument::Pos(body));
}
@ -181,28 +189,25 @@ fn bracket_call(p: &mut Parser) -> Expr {
inner = top;
}
Expr::Call(inner.v)
Some(Expr::Call(inner.v))
}
/// Parse one subheader of a bracketed function call.
fn bracket_subheader(p: &mut Parser) -> ExprCall {
fn bracket_subheader(p: &mut Parser) -> Option<ExprCall> {
p.start_group(Group::Subheader, TokenMode::Code);
let start = p.next_start();
let name = p.span_if(ident).unwrap_or_else(|| {
let what = "function name";
if p.eof() {
p.expected_at(what, start);
} else {
p.expected(what);
}
Ident(String::new()).with_span(start)
});
let name = p.span_if(ident);
if name.is_none() {
p.expected("function name");
}
let args = p.span(arguments);
p.end_group();
ExprCall { name, args }
Some(ExprCall {
callee: Box::new(name?.map(Expr::Ident)),
args,
})
}
/// Parse the body of a bracketed function call.
@ -213,78 +218,66 @@ fn bracket_body(p: &mut Parser) -> Tree {
tree
}
/// Parse a block expression: `{...}`.
fn block(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Brace, TokenMode::Code);
let expr = p.span_if(expr);
while !p.eof() {
p.unexpected();
}
p.end_group();
Some(Expr::Block(Box::new(expr?)))
/// Parse an identifier.
fn ident(p: &mut Parser) -> Option<Ident> {
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<Expr> {
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<Expr> {
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<Expr> {
let mut lhs = match p.span_if(|p| p.eat_map(UnOp::from_token)) {
Some(op) => {
let prec = op.v.precedence();
let expr = p.span_if(|p| expr_with(p, prec))?;
let span = op.span.join(expr.span);
let unary = Expr::Unary(ExprUnary { op, expr: Box::new(expr) });
unary.with_span(span)
}
None => p.span_if(primary)?,
};
/// Parse binary operations of the from `a (<op> b)*`.
fn binops(
p: &mut Parser,
operand: fn(&mut Parser) -> Option<Expr>,
op: fn(Token) -> Option<BinOp>,
) -> Option<Expr> {
let mut lhs = p.span_if(operand)?;
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<Expr> {
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<Expr> {
/// Parse a primary expression.
fn primary(p: &mut Parser) -> Option<Expr> {
let expr = match p.peek() {
// Template.
Some(Token::LeftBracket) => {
@ -342,19 +335,25 @@ fn template(p: &mut Parser) -> Expr {
Expr::Template(tree)
}
/// Parse a block expression: `{...}`.
fn block(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Brace, TokenMode::Code);
let expr = p.span_if(expr);
while !p.eof() {
p.unexpected();
}
p.end_group();
Some(Expr::Block(Box::new(expr?)))
}
/// Parse a parenthesized function call.
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Expr {
p.start_group(Group::Paren, TokenMode::Code);
let args = p.span(arguments);
p.end_group();
Expr::Call(ExprCall { name, args })
}
/// Parse an identifier.
fn ident(p: &mut Parser) -> Option<Ident> {
p.eat_map(|token| match token {
Token::Ident(id) => Some(Ident(id.into())),
_ => None,
Expr::Call(ExprCall {
callee: Box::new(name.map(Expr::Ident)),
args,
})
}
@ -388,14 +387,14 @@ fn stmt_let(p: &mut Parser) -> Option<Expr> {
if p.eat_if(Token::Eq) {
rhs = p.span_if(expr);
}
if !p.eof() {
p.expected_at("semicolon or line break", p.last_end());
}
} else {
p.expected("identifier");
}
if !p.eof() {
p.expected_at("semicolon or line break", p.last_end());
}
p.end_group();
Some(Expr::Let(ExprLet { pat: pat?, expr: rhs.map(Box::new) }))

View File

@ -118,6 +118,23 @@ impl Pretty for ExprDict {
}
}
/// A pair of a name and an expression: `pattern: dashed`.
#[derive(Debug, Clone, PartialEq)]
pub struct Named {
/// The name: `pattern`.
pub name: Spanned<Ident>,
/// The right-hand side of the pair: `dashed`.
pub expr: Spanned<Expr>,
}
impl Pretty for Named {
fn pretty(&self, p: &mut Printer) {
p.push_str(&self.name.v);
p.push_str(": ");
self.expr.v.pretty(p);
}
}
/// A template expression: `[*Hi* there!]`.
pub type ExprTemplate = Tree;
@ -127,18 +144,246 @@ pub type ExprGroup = Box<Spanned<Expr>>;
/// A block expression: `{1 + 2}`.
pub type ExprBlock = Box<Spanned<Expr>>;
/// A unary operation: `-x`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprUnary {
/// The operator: `-`.
pub op: Spanned<UnOp>,
/// The expression to operator on: `x`.
pub expr: Box<Spanned<Expr>>,
}
impl Pretty for ExprUnary {
fn pretty(&self, p: &mut Printer) {
self.op.v.pretty(p);
if self.op.v == UnOp::Not {
p.push_str(" ");
}
self.expr.v.pretty(p);
}
}
/// A unary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum UnOp {
/// The plus operator: `+`.
Pos,
/// The negation operator: `-`.
Neg,
/// The boolean `not`.
Not,
}
impl UnOp {
/// Try to convert the token into a unary operation.
pub fn from_token(token: Token) -> Option<Self> {
Some(match token {
Token::Plus => Self::Pos,
Token::Hyph => Self::Neg,
Token::Not => Self::Not,
_ => return None,
})
}
/// The precedence of this operator.
pub fn precedence(self) -> usize {
match self {
Self::Pos | Self::Neg => 8,
Self::Not => 4,
}
}
/// The string representation of this operation.
pub fn as_str(self) -> &'static str {
match self {
Self::Pos => "+",
Self::Neg => "-",
Self::Not => "not",
}
}
}
impl Pretty for UnOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(self.as_str());
}
}
/// A binary operation: `a + b`, `a / b`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBinary {
/// The left-hand side of the operation: `a`.
pub lhs: Box<Spanned<Expr>>,
/// The operator: `+`.
pub op: Spanned<BinOp>,
/// The right-hand side of the operation: `b`.
pub rhs: Box<Spanned<Expr>>,
}
impl Pretty for ExprBinary {
fn pretty(&self, p: &mut Printer) {
self.lhs.v.pretty(p);
p.push_str(" ");
self.op.v.pretty(p);
p.push_str(" ");
self.rhs.v.pretty(p);
}
}
/// A binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BinOp {
/// The addition operator: `+`.
Add,
/// The subtraction operator: `-`.
Sub,
/// The multiplication operator: `*`.
Mul,
/// The division operator: `/`.
Div,
/// The short-circuiting boolean `and`.
And,
/// The short-circuiting boolean `or`.
Or,
/// The equality operator: `==`.
Eq,
/// The inequality operator: `!=`.
Neq,
/// The less-than operator: `<`.
Lt,
/// The less-than or equal operator: `<=`.
Leq,
/// The greater-than operator: `>`.
Gt,
/// The greater-than or equal operator: `>=`.
Geq,
/// The assignment operator: `=`.
Assign,
/// The add-assign operator: `+=`.
AddAssign,
/// The subtract-assign oeprator: `-=`.
SubAssign,
/// The multiply-assign operator: `*=`.
MulAssign,
/// The divide-assign operator: `/=`.
DivAssign,
}
impl BinOp {
/// Try to convert the token into a binary operation.
pub fn from_token(token: Token) -> Option<Self> {
Some(match token {
Token::Plus => Self::Add,
Token::Hyph => Self::Sub,
Token::Star => Self::Mul,
Token::Slash => Self::Div,
Token::And => Self::And,
Token::Or => Self::Or,
Token::EqEq => Self::Eq,
Token::BangEq => Self::Neq,
Token::Lt => Self::Lt,
Token::LtEq => Self::Leq,
Token::Gt => Self::Gt,
Token::GtEq => Self::Geq,
Token::Eq => Self::Assign,
Token::PlusEq => Self::AddAssign,
Token::HyphEq => Self::SubAssign,
Token::StarEq => Self::MulAssign,
Token::SlashEq => Self::DivAssign,
_ => return None,
})
}
/// The precedence of this operator.
pub fn precedence(self) -> usize {
match self {
Self::Mul | Self::Div => 7,
Self::Add | Self::Sub => 6,
Self::Eq | Self::Neq | Self::Lt | Self::Leq | Self::Gt | Self::Geq => 5,
Self::And => 3,
Self::Or => 2,
Self::Assign
| Self::AddAssign
| Self::SubAssign
| Self::MulAssign
| Self::DivAssign => 1,
}
}
/// The associativity of this operator.
pub fn associativity(self) -> Associativity {
match self {
Self::Add
| Self::Sub
| Self::Mul
| Self::Div
| Self::And
| Self::Or
| Self::Eq
| Self::Neq
| Self::Lt
| Self::Leq
| Self::Gt
| Self::Geq => Associativity::Left,
Self::Assign
| Self::AddAssign
| Self::SubAssign
| Self::MulAssign
| Self::DivAssign => Associativity::Right,
}
}
/// The string representation of this operation.
pub fn as_str(self) -> &'static str {
match self {
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
Self::And => "and",
Self::Or => "or",
Self::Eq => "==",
Self::Neq => "!=",
Self::Lt => "<",
Self::Leq => "<=",
Self::Gt => ">",
Self::Geq => ">=",
Self::Assign => "=",
Self::AddAssign => "+=",
Self::SubAssign => "-=",
Self::MulAssign => "*=",
Self::DivAssign => "/=",
}
}
}
impl Pretty for BinOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(self.as_str());
}
}
/// The associativity of a binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Associativity {
/// Left-associative: `a + b + c` is equivalent to `(a + b) + c`.
Left,
/// Right-associative: `a = b = c` is equivalent to `a = (b = c)`.
Right,
}
/// An invocation of a function: `[foo ...]`, `foo(...)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprCall {
/// The name of the function.
pub name: Spanned<Ident>,
/// The callee of the function.
pub callee: Box<Spanned<Expr>>,
/// The arguments to the function.
pub args: Spanned<ExprArgs>,
}
impl Pretty for ExprCall {
fn pretty(&self, p: &mut Printer) {
p.push_str(&self.name.v);
self.callee.v.pretty(p);
p.push_str("(");
self.args.v.pretty(p);
p.push_str(")");
@ -154,7 +399,7 @@ pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) {
}
// Function name.
p.push_str(&call.name.v);
call.callee.v.pretty(p);
// Find out whether this can be written with a body or as a chain.
//
@ -216,102 +461,6 @@ impl Pretty for Argument {
}
}
/// A pair of a name and an expression: `pattern: dashed`.
#[derive(Debug, Clone, PartialEq)]
pub struct Named {
/// The name: `pattern`.
pub name: Spanned<Ident>,
/// The right-hand side of the pair: `dashed`.
pub expr: Spanned<Expr>,
}
impl Pretty for Named {
fn pretty(&self, p: &mut Printer) {
p.push_str(&self.name.v);
p.push_str(": ");
self.expr.v.pretty(p);
}
}
/// A unary operation: `-x`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprUnary {
/// The operator: `-`.
pub op: Spanned<UnOp>,
/// The expression to operator on: `x`.
pub expr: Box<Spanned<Expr>>,
}
impl Pretty for ExprUnary {
fn pretty(&self, p: &mut Printer) {
self.op.v.pretty(p);
self.expr.v.pretty(p);
}
}
/// A unary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum UnOp {
/// The plus operator: `+`.
Pos,
/// The negation operator: `-`.
Neg,
}
impl Pretty for UnOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(match self {
Self::Pos => "+",
Self::Neg => "-",
});
}
}
/// A binary operation: `a + b`, `a / b`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBinary {
/// The left-hand side of the operation: `a`.
pub lhs: Box<Spanned<Expr>>,
/// The operator: `+`.
pub op: Spanned<BinOp>,
/// The right-hand side of the operation: `b`.
pub rhs: Box<Spanned<Expr>>,
}
impl Pretty for ExprBinary {
fn pretty(&self, p: &mut Printer) {
self.lhs.v.pretty(p);
p.push_str(" ");
self.op.v.pretty(p);
p.push_str(" ");
self.rhs.v.pretty(p);
}
}
/// A binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BinOp {
/// The addition operator: `+`.
Add,
/// The subtraction operator: `-`.
Sub,
/// The multiplication operator: `*`.
Mul,
/// The division operator: `/`.
Div,
}
impl Pretty for BinOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(match self {
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
});
}
}
/// A let expression: `let x = 1`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprLet {

View File

@ -1,10 +1,7 @@
use std::cell::Cell;
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Range;
#[cfg(test)]
use std::cell::Cell;
#[cfg(test)]
thread_local! {
static CMP_SPANS: Cell<bool> = Cell::new(true);
}
@ -148,10 +145,27 @@ impl Span {
self.start.to_usize() .. self.end.to_usize()
}
/// Run some code with span comparisons disabled.
pub fn without_cmp<F, T>(f: F) -> T
where
F: FnOnce() -> T,
{
let prev = Self::cmp();
Self::set_cmp(false);
let val = f();
Self::set_cmp(prev);
val
}
/// Whether spans will currently be compared.
fn cmp() -> bool {
CMP_SPANS.with(Cell::get)
}
/// Whether spans should be compared.
///
/// When set to `false` comparisons with `PartialEq` ignore spans.
#[cfg(test)]
#[allow(unused)]
pub(crate) fn set_cmp(cmp: bool) {
fn set_cmp(cmp: bool) {
CMP_SPANS.with(|cell| cell.set(cmp));
}
}
@ -169,12 +183,7 @@ impl Eq for Span {}
impl PartialEq for Span {
fn eq(&self, other: &Self) -> bool {
#[cfg(test)]
if !CMP_SPANS.with(Cell::get) {
return true;
}
self.start == other.start && self.end == other.end
!Self::cmp() || (self.start == other.start && self.end == other.end)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -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 )]

View File

@ -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%}

View File

@ -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

View File

@ -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<Frame>) {
fn test_part(src: &str, i: usize, lines: u32, env: &mut Env) -> (bool, Vec<Frame>) {
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<Frame>) {
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<RefCell<bool>>) {
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::<Value>(ctx, "left-hand side");
let rhs = args.require::<Value>(ctx, "right-hand side");
if lhs != rhs {
@ -302,13 +304,15 @@ fn register_helpers(scope: &mut Scope, panicked: Rc<RefCell<bool>>) {
}
};
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<Diag>, map: &LineMap) {
let start = map.location(diag.span.start).unwrap();
let end = map.location(diag.span.end).unwrap();
fn print_diag(diag: &Spanned<Diag>, 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);
}