mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Many more expressions 🥗
Boolean, equality, comparison and assignment expression parsing and evaluation.
This commit is contained in:
parent
0de4f3ed7b
commit
ac788f2082
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
134
src/eval/mod.rs
134
src/eval/mod.rs
@ -164,13 +164,13 @@ impl Eval for Spanned<&Expr> {
|
||||
Value::Error
|
||||
}
|
||||
},
|
||||
Expr::Bool(v) => Value::Bool(*v),
|
||||
Expr::Int(v) => Value::Int(*v),
|
||||
Expr::Float(v) => Value::Float(*v),
|
||||
Expr::Length(v, unit) => Value::Length(Length::with_unit(*v, *unit)),
|
||||
Expr::Angle(v, unit) => Value::Angle(Angle::with_unit(*v, *unit)),
|
||||
Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
|
||||
Expr::Color(v) => Value::Color(Color::Rgba(*v)),
|
||||
&Expr::Bool(v) => Value::Bool(v),
|
||||
&Expr::Int(v) => Value::Int(v),
|
||||
&Expr::Float(v) => Value::Float(v),
|
||||
&Expr::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
|
||||
&Expr::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
|
||||
&Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
|
||||
&Expr::Color(v) => Value::Color(Color::Rgba(v)),
|
||||
Expr::Str(v) => Value::Str(v.clone()),
|
||||
Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)),
|
||||
Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)),
|
||||
@ -210,16 +210,27 @@ impl Eval for Spanned<&ExprUnary> {
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
let value = self.v.expr.as_ref().eval(ctx);
|
||||
|
||||
if let Value::Error = value {
|
||||
if value == Value::Error {
|
||||
return Value::Error;
|
||||
}
|
||||
|
||||
let span = self.v.op.span.join(self.v.expr.span);
|
||||
match self.v.op.v {
|
||||
UnOp::Pos => ops::pos(ctx, span, value),
|
||||
UnOp::Neg => ops::neg(ctx, span, value),
|
||||
let ty = value.type_name();
|
||||
let out = match self.v.op.v {
|
||||
UnOp::Pos => ops::pos(value),
|
||||
UnOp::Neg => ops::neg(value),
|
||||
UnOp::Not => ops::not(value),
|
||||
};
|
||||
|
||||
if out == Value::Error {
|
||||
ctx.diag(error!(
|
||||
self.span,
|
||||
"cannot apply '{}' to {}",
|
||||
self.v.op.v.as_str(),
|
||||
ty,
|
||||
));
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,20 +238,90 @@ impl Eval for Spanned<&ExprBinary> {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
let lhs = self.v.lhs.as_ref().eval(ctx);
|
||||
let rhs = self.v.rhs.as_ref().eval(ctx);
|
||||
match self.v.op.v {
|
||||
BinOp::Add => self.apply(ctx, ops::add),
|
||||
BinOp::Sub => self.apply(ctx, ops::sub),
|
||||
BinOp::Mul => self.apply(ctx, ops::mul),
|
||||
BinOp::Div => self.apply(ctx, ops::div),
|
||||
BinOp::And => self.apply(ctx, ops::and),
|
||||
BinOp::Or => self.apply(ctx, ops::or),
|
||||
BinOp::Eq => self.apply(ctx, ops::eq),
|
||||
BinOp::Neq => self.apply(ctx, ops::neq),
|
||||
BinOp::Lt => self.apply(ctx, ops::lt),
|
||||
BinOp::Leq => self.apply(ctx, ops::leq),
|
||||
BinOp::Gt => self.apply(ctx, ops::gt),
|
||||
BinOp::Geq => self.apply(ctx, ops::geq),
|
||||
BinOp::Assign => self.assign(ctx, |_, b| b),
|
||||
BinOp::AddAssign => self.assign(ctx, ops::add),
|
||||
BinOp::SubAssign => self.assign(ctx, ops::sub),
|
||||
BinOp::MulAssign => self.assign(ctx, ops::mul),
|
||||
BinOp::DivAssign => self.assign(ctx, ops::div),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned<&ExprBinary> {
|
||||
/// Apply a basic binary operation.
|
||||
fn apply<F>(&self, ctx: &mut EvalContext, op: F) -> Value
|
||||
where
|
||||
F: FnOnce(Value, Value) -> Value,
|
||||
{
|
||||
let lhs = self.v.lhs.eval(ctx);
|
||||
|
||||
// Short-circuit boolean operations.
|
||||
match (self.v.op.v, &lhs) {
|
||||
(BinOp::And, Value::Bool(false)) => return Value::Bool(false),
|
||||
(BinOp::Or, Value::Bool(true)) => return Value::Bool(true),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let rhs = self.v.rhs.eval(ctx);
|
||||
|
||||
if lhs == Value::Error || rhs == Value::Error {
|
||||
return Value::Error;
|
||||
}
|
||||
|
||||
let span = self.v.lhs.span.join(self.v.rhs.span);
|
||||
match self.v.op.v {
|
||||
BinOp::Add => ops::add(ctx, span, lhs, rhs),
|
||||
BinOp::Sub => ops::sub(ctx, span, lhs, rhs),
|
||||
BinOp::Mul => ops::mul(ctx, span, lhs, rhs),
|
||||
BinOp::Div => ops::div(ctx, span, lhs, rhs),
|
||||
let lhty = lhs.type_name();
|
||||
let rhty = rhs.type_name();
|
||||
let out = op(lhs, rhs);
|
||||
if out == Value::Error {
|
||||
ctx.diag(error!(
|
||||
self.span,
|
||||
"cannot apply '{}' to {} and {}",
|
||||
self.v.op.v.as_str(),
|
||||
lhty,
|
||||
rhty,
|
||||
));
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// Apply an assignment operation.
|
||||
fn assign<F>(&self, ctx: &mut EvalContext, op: F) -> Value
|
||||
where
|
||||
F: FnOnce(Value, Value) -> Value,
|
||||
{
|
||||
let rhs = self.v.rhs.eval(ctx);
|
||||
let span = self.v.lhs.span;
|
||||
|
||||
if let Expr::Ident(id) = &self.v.lhs.v {
|
||||
if let Some(slot) = ctx.scopes.get_mut(id) {
|
||||
let lhs = std::mem::replace(slot, Value::None);
|
||||
*slot = op(lhs, rhs);
|
||||
return Value::None;
|
||||
} else {
|
||||
if ctx.scopes.is_const(id) {
|
||||
ctx.diag(error!(span, "cannot assign to constant"));
|
||||
} else {
|
||||
ctx.diag(error!(span, "unknown variable"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.diag(error!(span, "cannot assign to this expression"));
|
||||
}
|
||||
|
||||
Value::Error
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,20 +344,21 @@ impl Eval for Spanned<&ExprIf> {
|
||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||
let condition = self.v.condition.eval(ctx);
|
||||
if let Value::Bool(boolean) = condition {
|
||||
if boolean {
|
||||
return if boolean {
|
||||
self.v.if_body.eval(ctx)
|
||||
} else if let Some(expr) = &self.v.else_body {
|
||||
expr.eval(ctx)
|
||||
} else {
|
||||
Value::None
|
||||
}
|
||||
} else {
|
||||
};
|
||||
} else if condition != Value::Error {
|
||||
ctx.diag(error!(
|
||||
self.v.condition.span,
|
||||
"expected boolean, found {}",
|
||||
condition.type_name()
|
||||
condition.type_name(),
|
||||
));
|
||||
Value::Error
|
||||
}
|
||||
|
||||
Value::Error
|
||||
}
|
||||
}
|
||||
|
179
src/eval/ops.rs
179
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<T, A>(mut a: T, b: T) -> T
|
||||
where
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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"));
|
||||
|
179
src/parse/mod.rs
179
src/parse/mod.rs
@ -49,7 +49,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
let node = match p.peek()? {
|
||||
// Bracket call.
|
||||
Token::LeftBracket => {
|
||||
return Some(Node::Expr(bracket_call(p)));
|
||||
return Some(Node::Expr(bracket_call(p)?));
|
||||
}
|
||||
|
||||
// Code block.
|
||||
@ -153,22 +153,30 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String {
|
||||
}
|
||||
|
||||
/// Parse a bracketed function call.
|
||||
fn bracket_call(p: &mut Parser) -> Expr {
|
||||
fn bracket_call(p: &mut Parser) -> Option<Expr> {
|
||||
p.start_group(Group::Bracket, TokenMode::Code);
|
||||
|
||||
// One header is guaranteed, but there may be more (through chaining).
|
||||
let mut outer = vec![];
|
||||
let mut inner = p.span(bracket_subheader);
|
||||
let mut inner = p.span_if(bracket_subheader);
|
||||
|
||||
while p.eat_if(Token::Pipe) {
|
||||
outer.push(inner);
|
||||
inner = p.span(bracket_subheader);
|
||||
if let Some(new) = p.span_if(bracket_subheader) {
|
||||
outer.extend(inner);
|
||||
inner = Some(new);
|
||||
}
|
||||
}
|
||||
|
||||
p.end_group();
|
||||
|
||||
if p.peek() == Some(Token::LeftBracket) {
|
||||
let body = p.span(|p| Expr::Template(bracket_body(p)));
|
||||
let body = if p.peek() == Some(Token::LeftBracket) {
|
||||
Some(p.span(|p| Expr::Template(bracket_body(p))))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut inner = inner?;
|
||||
if let Some(body) = body {
|
||||
inner.span.expand(body.span);
|
||||
inner.v.args.v.push(Argument::Pos(body));
|
||||
}
|
||||
@ -181,28 +189,25 @@ fn bracket_call(p: &mut Parser) -> Expr {
|
||||
inner = top;
|
||||
}
|
||||
|
||||
Expr::Call(inner.v)
|
||||
Some(Expr::Call(inner.v))
|
||||
}
|
||||
|
||||
/// Parse one subheader of a bracketed function call.
|
||||
fn bracket_subheader(p: &mut Parser) -> ExprCall {
|
||||
fn bracket_subheader(p: &mut Parser) -> Option<ExprCall> {
|
||||
p.start_group(Group::Subheader, TokenMode::Code);
|
||||
|
||||
let start = p.next_start();
|
||||
let name = p.span_if(ident).unwrap_or_else(|| {
|
||||
let what = "function name";
|
||||
if p.eof() {
|
||||
p.expected_at(what, start);
|
||||
} else {
|
||||
p.expected(what);
|
||||
}
|
||||
Ident(String::new()).with_span(start)
|
||||
});
|
||||
let name = p.span_if(ident);
|
||||
if name.is_none() {
|
||||
p.expected("function name");
|
||||
}
|
||||
|
||||
let args = p.span(arguments);
|
||||
p.end_group();
|
||||
|
||||
ExprCall { name, args }
|
||||
Some(ExprCall {
|
||||
callee: Box::new(name?.map(Expr::Ident)),
|
||||
args,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse the body of a bracketed function call.
|
||||
@ -213,78 +218,66 @@ fn bracket_body(p: &mut Parser) -> Tree {
|
||||
tree
|
||||
}
|
||||
|
||||
/// Parse a block expression: `{...}`.
|
||||
fn block(p: &mut Parser) -> Option<Expr> {
|
||||
p.start_group(Group::Brace, TokenMode::Code);
|
||||
let expr = p.span_if(expr);
|
||||
while !p.eof() {
|
||||
p.unexpected();
|
||||
}
|
||||
p.end_group();
|
||||
Some(Expr::Block(Box::new(expr?)))
|
||||
/// Parse an 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) }))
|
||||
|
@ -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 {
|
||||
|
@ -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 |
@ -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 )]
|
||||
|
@ -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%}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user