mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +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,
|
Strong,
|
||||||
/// Emphasized text.
|
/// Emphasized text.
|
||||||
Emph,
|
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.
|
/// Construct a diagnostic with [`Error`](Level::Error) level.
|
||||||
|
@ -1,32 +1,26 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::diag::Deco;
|
|
||||||
|
|
||||||
impl Eval for Spanned<&ExprCall> {
|
impl Eval for Spanned<&ExprCall> {
|
||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let name = &self.v.name.v;
|
let callee = self.v.callee.eval(ctx);
|
||||||
let span = self.v.name.span;
|
|
||||||
|
|
||||||
if let Some(value) = ctx.scopes.get(name) {
|
if let Value::Func(func) = callee {
|
||||||
if let Value::Func(func) = value {
|
let func = func.clone();
|
||||||
let func = func.clone();
|
let mut args = self.v.args.as_ref().eval(ctx);
|
||||||
ctx.deco(Deco::Resolved.with_span(span));
|
let returned = func(ctx, &mut args);
|
||||||
|
args.finish(ctx);
|
||||||
|
|
||||||
let mut args = self.v.args.as_ref().eval(ctx);
|
return returned;
|
||||||
let returned = func(ctx, &mut args);
|
} else if callee != Value::Error {
|
||||||
args.finish(ctx);
|
ctx.diag(error!(
|
||||||
|
self.v.callee.span,
|
||||||
return returned;
|
"expected function, found {}",
|
||||||
} else {
|
callee.type_name(),
|
||||||
let ty = value.type_name();
|
));
|
||||||
ctx.diag(error!(span, "expected function, found {}", ty));
|
|
||||||
}
|
|
||||||
} else if !name.is_empty() {
|
|
||||||
ctx.diag(error!(span, "unknown function"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.deco(Deco::Unresolved.with_span(span));
|
|
||||||
Value::Error
|
Value::Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
134
src/eval/mod.rs
134
src/eval/mod.rs
@ -164,13 +164,13 @@ impl Eval for Spanned<&Expr> {
|
|||||||
Value::Error
|
Value::Error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Expr::Bool(v) => Value::Bool(*v),
|
&Expr::Bool(v) => Value::Bool(v),
|
||||||
Expr::Int(v) => Value::Int(*v),
|
&Expr::Int(v) => Value::Int(v),
|
||||||
Expr::Float(v) => Value::Float(*v),
|
&Expr::Float(v) => Value::Float(v),
|
||||||
Expr::Length(v, unit) => Value::Length(Length::with_unit(*v, *unit)),
|
&Expr::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
|
||||||
Expr::Angle(v, unit) => Value::Angle(Angle::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::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
|
||||||
Expr::Color(v) => Value::Color(Color::Rgba(*v)),
|
&Expr::Color(v) => Value::Color(Color::Rgba(v)),
|
||||||
Expr::Str(v) => Value::Str(v.clone()),
|
Expr::Str(v) => Value::Str(v.clone()),
|
||||||
Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)),
|
Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)),
|
||||||
Expr::Dict(v) => Value::Dict(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 {
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let value = self.v.expr.as_ref().eval(ctx);
|
let value = self.v.expr.as_ref().eval(ctx);
|
||||||
|
if value == Value::Error {
|
||||||
if let Value::Error = value {
|
|
||||||
return Value::Error;
|
return Value::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = self.v.op.span.join(self.v.expr.span);
|
let ty = value.type_name();
|
||||||
match self.v.op.v {
|
let out = match self.v.op.v {
|
||||||
UnOp::Pos => ops::pos(ctx, span, value),
|
UnOp::Pos => ops::pos(value),
|
||||||
UnOp::Neg => ops::neg(ctx, span, 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;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let lhs = self.v.lhs.as_ref().eval(ctx);
|
match self.v.op.v {
|
||||||
let rhs = self.v.rhs.as_ref().eval(ctx);
|
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 {
|
if lhs == Value::Error || rhs == Value::Error {
|
||||||
return Value::Error;
|
return Value::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = self.v.lhs.span.join(self.v.rhs.span);
|
let lhty = lhs.type_name();
|
||||||
match self.v.op.v {
|
let rhty = rhs.type_name();
|
||||||
BinOp::Add => ops::add(ctx, span, lhs, rhs),
|
let out = op(lhs, rhs);
|
||||||
BinOp::Sub => ops::sub(ctx, span, lhs, rhs),
|
if out == Value::Error {
|
||||||
BinOp::Mul => ops::mul(ctx, span, lhs, rhs),
|
ctx.diag(error!(
|
||||||
BinOp::Div => ops::div(ctx, span, lhs, rhs),
|
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 {
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let condition = self.v.condition.eval(ctx);
|
let condition = self.v.condition.eval(ctx);
|
||||||
if let Value::Bool(boolean) = condition {
|
if let Value::Bool(boolean) = condition {
|
||||||
if boolean {
|
return if boolean {
|
||||||
self.v.if_body.eval(ctx)
|
self.v.if_body.eval(ctx)
|
||||||
} else if let Some(expr) = &self.v.else_body {
|
} else if let Some(expr) = &self.v.else_body {
|
||||||
expr.eval(ctx)
|
expr.eval(ctx)
|
||||||
} else {
|
} else {
|
||||||
Value::None
|
Value::None
|
||||||
}
|
};
|
||||||
} else {
|
} else if condition != Value::Error {
|
||||||
ctx.diag(error!(
|
ctx.diag(error!(
|
||||||
self.v.condition.span,
|
self.v.condition.span,
|
||||||
"expected boolean, found {}",
|
"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 super::*;
|
||||||
|
use Value::*;
|
||||||
|
|
||||||
/// Apply plus operator to a value.
|
/// Apply the plus operator to a value.
|
||||||
pub fn pos(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
|
pub fn pos(value: Value) -> Value {
|
||||||
if value.is_numeric() {
|
match value {
|
||||||
value
|
Int(v) => Int(v),
|
||||||
} else {
|
Float(v) => Float(v),
|
||||||
ctx.diag(error!(
|
Length(v) => Length(v),
|
||||||
span,
|
Angle(v) => Angle(v),
|
||||||
"cannot apply plus operator to {}",
|
Relative(v) => Relative(v),
|
||||||
value.type_name()
|
Linear(v) => Linear(v),
|
||||||
));
|
_ => Error,
|
||||||
Value::Error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the negation of a value.
|
/// Compute the negation of a value.
|
||||||
pub fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
|
pub fn neg(value: Value) -> Value {
|
||||||
use Value::*;
|
|
||||||
match value {
|
match value {
|
||||||
Int(v) => Int(-v),
|
Int(v) => Int(-v),
|
||||||
Float(v) => Float(-v),
|
Float(v) => Float(-v),
|
||||||
@ -24,18 +23,13 @@ pub fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
|
|||||||
Angle(v) => Angle(-v),
|
Angle(v) => Angle(-v),
|
||||||
Relative(v) => Relative(-v),
|
Relative(v) => Relative(-v),
|
||||||
Linear(v) => Linear(-v),
|
Linear(v) => Linear(-v),
|
||||||
v => {
|
_ => Error,
|
||||||
ctx.diag(error!(span, "cannot negate {}", v.type_name()));
|
|
||||||
Value::Error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the sum of two values.
|
/// Compute the sum of two values.
|
||||||
pub fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
|
pub fn add(lhs: Value, rhs: Value) -> Value {
|
||||||
use Value::*;
|
|
||||||
match (lhs, rhs) {
|
match (lhs, rhs) {
|
||||||
// Numeric types to themselves.
|
|
||||||
(Int(a), Int(b)) => Int(a + b),
|
(Int(a), Int(b)) => Int(a + b),
|
||||||
(Int(a), Float(b)) => Float(a as f64 + b),
|
(Int(a), Float(b)) => Float(a as f64 + b),
|
||||||
(Float(a), Int(b)) => Float(a + b as f64),
|
(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), Length(b)) => Linear(a + b),
|
||||||
(Linear(a), Relative(b)) => Linear(a + b),
|
(Linear(a), Relative(b)) => Linear(a + b),
|
||||||
(Linear(a), Linear(b)) => Linear(a + b),
|
(Linear(a), Linear(b)) => Linear(a + b),
|
||||||
|
|
||||||
// Complex data types to themselves.
|
|
||||||
(Str(a), Str(b)) => Str(a + &b),
|
(Str(a), Str(b)) => Str(a + &b),
|
||||||
(Array(a), Array(b)) => Array(concat(a, b)),
|
(Array(a), Array(b)) => Array(concat(a, b)),
|
||||||
(Dict(a), Dict(b)) => Dict(concat(a, b)),
|
(Dict(a), Dict(b)) => Dict(concat(a, b)),
|
||||||
(Template(a), Template(b)) => Template(concat(a, b)),
|
(Template(a), Template(b)) => Template(concat(a, b)),
|
||||||
|
_ => Error,
|
||||||
(a, b) => {
|
|
||||||
ctx.diag(error!(
|
|
||||||
span,
|
|
||||||
"cannot add {} and {}",
|
|
||||||
a.type_name(),
|
|
||||||
b.type_name()
|
|
||||||
));
|
|
||||||
Value::Error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the difference of two values.
|
/// Compute the difference of two values.
|
||||||
pub fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
|
pub fn sub(lhs: Value, rhs: Value) -> Value {
|
||||||
use Value::*;
|
|
||||||
match (lhs, rhs) {
|
match (lhs, rhs) {
|
||||||
// Numbers from themselves.
|
|
||||||
(Int(a), Int(b)) => Int(a - b),
|
(Int(a), Int(b)) => Int(a - b),
|
||||||
(Int(a), Float(b)) => Float(a as f64 - b),
|
(Int(a), Float(b)) => Float(a as f64 - b),
|
||||||
(Float(a), Int(b)) => Float(a - b as f64),
|
(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), Length(b)) => Linear(a - b),
|
||||||
(Linear(a), Relative(b)) => Linear(a - b),
|
(Linear(a), Relative(b)) => Linear(a - b),
|
||||||
(Linear(a), Linear(b)) => Linear(a - b),
|
(Linear(a), Linear(b)) => Linear(a - b),
|
||||||
|
_ => Error,
|
||||||
(a, b) => {
|
|
||||||
ctx.diag(error!(
|
|
||||||
span,
|
|
||||||
"cannot subtract {1} from {0}",
|
|
||||||
a.type_name(),
|
|
||||||
b.type_name()
|
|
||||||
));
|
|
||||||
Value::Error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the product of two values.
|
/// Compute the product of two values.
|
||||||
pub fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
|
pub fn mul(lhs: Value, rhs: Value) -> Value {
|
||||||
use Value::*;
|
|
||||||
match (lhs, rhs) {
|
match (lhs, rhs) {
|
||||||
// Numeric types with numbers.
|
|
||||||
(Int(a), Int(b)) => Int(a * b),
|
(Int(a), Int(b)) => Int(a * b),
|
||||||
(Int(a), Float(b)) => Float(a as f64 * b),
|
(Int(a), Float(b)) => Float(a as f64 * b),
|
||||||
(Float(a), Int(b)) => Float(a * b as f64),
|
(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),
|
(Linear(a), Float(b)) => Linear(a * b),
|
||||||
(Int(a), Linear(b)) => Linear(a as f64 * b),
|
(Int(a), Linear(b)) => Linear(a as f64 * b),
|
||||||
(Float(a), Linear(b)) => Linear(a * b),
|
(Float(a), Linear(b)) => Linear(a * b),
|
||||||
|
_ => Error,
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the quotient of two values.
|
/// Compute the quotient of two values.
|
||||||
pub fn div(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
|
pub fn div(lhs: Value, rhs: Value) -> Value {
|
||||||
use Value::*;
|
|
||||||
match (lhs, rhs) {
|
match (lhs, rhs) {
|
||||||
// Numeric types by numbers.
|
|
||||||
(Int(a), Int(b)) => Float(a as f64 / b as f64),
|
(Int(a), Int(b)) => Float(a as f64 / b as f64),
|
||||||
(Int(a), Float(b)) => Float(a as f64 / b),
|
(Int(a), Float(b)) => Float(a as f64 / b),
|
||||||
(Float(a), Int(b)) => Float(a / b as f64),
|
(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),
|
(Relative(a), Float(b)) => Relative(a / b),
|
||||||
(Linear(a), Int(b)) => Linear(a / b as f64),
|
(Linear(a), Int(b)) => Linear(a / b as f64),
|
||||||
(Linear(a), Float(b)) => Linear(a / b),
|
(Linear(a), Float(b)) => Linear(a / b),
|
||||||
|
_ => Error,
|
||||||
(a, b) => {
|
|
||||||
ctx.diag(error!(
|
|
||||||
span,
|
|
||||||
"cannot divide {} by {}",
|
|
||||||
a.type_name(),
|
|
||||||
b.type_name()
|
|
||||||
));
|
|
||||||
Value::Error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute the logical "not" of a value.
|
||||||
|
pub fn not(value: Value) -> Value {
|
||||||
|
match value {
|
||||||
|
Bool(b) => Bool(!b),
|
||||||
|
_ => Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the logical "and" of two values.
|
||||||
|
pub fn and(lhs: Value, rhs: Value) -> Value {
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(Bool(a), Bool(b)) => Bool(a && b),
|
||||||
|
_ => Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the logical "or" of two values.
|
||||||
|
pub fn or(lhs: Value, rhs: Value) -> Value {
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(Bool(a), Bool(b)) => Bool(a || b),
|
||||||
|
_ => Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute whether two values are equal.
|
||||||
|
pub fn eq(lhs: Value, rhs: Value) -> Value {
|
||||||
|
Bool(value_eq(&lhs, &rhs))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute whether two values are equal.
|
||||||
|
pub fn neq(lhs: Value, rhs: Value) -> Value {
|
||||||
|
Bool(!value_eq(&lhs, &rhs))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively compute whether two values are equal.
|
||||||
|
fn value_eq(lhs: &Value, rhs: &Value) -> bool {
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(&Int(a), &Float(b)) => a as f64 == b,
|
||||||
|
(&Float(a), &Int(b)) => a == b as f64,
|
||||||
|
(&Length(a), &Linear(b)) => a == b.abs && b.rel.is_zero(),
|
||||||
|
(&Relative(a), &Linear(b)) => a == b.rel && b.abs.is_zero(),
|
||||||
|
(&Linear(a), &Length(b)) => a.abs == b && a.rel.is_zero(),
|
||||||
|
(&Linear(a), &Relative(b)) => a.rel == b && a.abs.is_zero(),
|
||||||
|
(Array(a), Array(b)) => array_eq(a, b),
|
||||||
|
(Dict(a), Dict(b)) => dict_eq(a, b),
|
||||||
|
(Template(a), Template(b)) => Span::without_cmp(|| a == b),
|
||||||
|
(a, b) => a == b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute whether two arrays are equal.
|
||||||
|
fn array_eq(a: &ValueArray, b: &ValueArray) -> bool {
|
||||||
|
a.len() == b.len() && a.iter().zip(b).all(|(x, y)| value_eq(x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute whether two dictionaries are equal.
|
||||||
|
fn dict_eq(a: &ValueDict, b: &ValueDict) -> bool {
|
||||||
|
a.len() == b.len()
|
||||||
|
&& a.iter().all(|(k, x)| b.get(k).map_or(false, |y| value_eq(x, y)))
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! comparison {
|
||||||
|
($name:ident, $op:tt) => {
|
||||||
|
/// Compute how a value compares with another value.
|
||||||
|
pub fn $name(lhs: Value, rhs: Value) -> Value {
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(Int(a), Int(b)) => Bool(a $op b),
|
||||||
|
(Int(a), Float(b)) => Bool((a as f64) $op b),
|
||||||
|
(Float(a), Int(b)) => Bool(a $op b as f64),
|
||||||
|
(Float(a), Float(b)) => Bool(a $op b),
|
||||||
|
(Angle(a), Angle(b)) => Bool(a $op b),
|
||||||
|
(Length(a), Length(b)) => Bool(a $op b),
|
||||||
|
_ => Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
comparison!(lt, <);
|
||||||
|
comparison!(leq, <=);
|
||||||
|
comparison!(gt, >);
|
||||||
|
comparison!(geq, >=);
|
||||||
|
|
||||||
/// Concatenate two collections.
|
/// Concatenate two collections.
|
||||||
fn concat<T, A>(mut a: T, b: T) -> T
|
fn concat<T, A>(mut a: T, b: T) -> T
|
||||||
where
|
where
|
||||||
|
@ -21,7 +21,12 @@ impl<'a> Scopes<'a> {
|
|||||||
Self { top: Scope::new(), scopes: vec![], base }
|
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> {
|
pub fn get(&self, var: &str) -> Option<&Value> {
|
||||||
iter::once(&self.top)
|
iter::once(&self.top)
|
||||||
.chain(&self.scopes)
|
.chain(&self.scopes)
|
||||||
@ -29,9 +34,18 @@ impl<'a> Scopes<'a> {
|
|||||||
.find_map(|scope| scope.get(var))
|
.find_map(|scope| scope.get(var))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a variable in the active scope.
|
/// Get a mutable reference to a variable.
|
||||||
pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
pub fn get_mut(&mut self, var: &str) -> Option<&mut Value> {
|
||||||
self.top.set(var, 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()
|
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.
|
/// Look up the value of a variable.
|
||||||
pub fn get(&self, var: &str) -> Option<&Value> {
|
pub fn get(&self, var: &str) -> Option<&Value> {
|
||||||
self.values.get(var)
|
self.values.get(var)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store the value for a variable.
|
/// Get a mutable reference to a variable.
|
||||||
pub fn set(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
pub fn get_mut(&mut self, var: &str) -> Option<&mut Value> {
|
||||||
self.values.insert(var.into(), value.into());
|
self.values.get_mut(var)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,18 +77,6 @@ impl Value {
|
|||||||
Self::Error => "error",
|
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 {
|
impl Eval for &Value {
|
||||||
|
@ -81,6 +81,11 @@ impl Length {
|
|||||||
Self { raw: self.raw.max(other.raw) }
|
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.
|
/// Whether the length is finite.
|
||||||
pub fn is_finite(self) -> bool {
|
pub fn is_finite(self) -> bool {
|
||||||
self.raw.is_finite()
|
self.raw.is_finite()
|
||||||
|
@ -26,9 +26,9 @@ impl Linear {
|
|||||||
self.rel.resolve(length) + self.abs
|
self.rel.resolve(length) + self.abs
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this linear's relative part is zero.
|
/// Whether both parts are zero.
|
||||||
pub fn is_absolute(self) -> bool {
|
pub fn is_zero(self) -> bool {
|
||||||
self.rel == Relative::ZERO
|
self.rel.is_zero() && self.abs.is_zero()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,12 +27,17 @@ impl Relative {
|
|||||||
/// Resolve this relative to the given `length`.
|
/// Resolve this relative to the given `length`.
|
||||||
pub fn resolve(self, length: Length) -> Length {
|
pub fn resolve(self, length: Length) -> Length {
|
||||||
// Zero wins over infinity.
|
// Zero wins over infinity.
|
||||||
if self.0 == 0.0 {
|
if self.is_zero() {
|
||||||
Length::ZERO
|
Length::ZERO
|
||||||
} else {
|
} else {
|
||||||
self.get() * length
|
self.get() * length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the ratio is zero.
|
||||||
|
pub fn is_zero(self) -> bool {
|
||||||
|
self.0 == 0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Relative {
|
impl Display for Relative {
|
||||||
|
@ -23,10 +23,10 @@ pub fn new() -> Scope {
|
|||||||
let mut std = Scope::new();
|
let mut std = Scope::new();
|
||||||
macro_rules! set {
|
macro_rules! set {
|
||||||
(func: $name:expr, $func:expr) => {
|
(func: $name:expr, $func:expr) => {
|
||||||
std.set($name, ValueFunc::new($name, $func))
|
std.define($name, ValueFunc::new($name, $func))
|
||||||
};
|
};
|
||||||
(any: $var:expr, $any:expr) => {
|
(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();
|
let snapshot = ctx.state.clone();
|
||||||
|
|
||||||
if let Some(linear) = args.find::<Linear>(ctx) {
|
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.size = linear.abs;
|
||||||
ctx.state.font.scale = Relative::ONE.into();
|
ctx.state.font.scale = Relative::ONE.into();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::diag::Deco;
|
|
||||||
|
|
||||||
/// Parse the arguments to a function call.
|
/// Parse the arguments to a function call.
|
||||||
pub fn arguments(p: &mut Parser) -> ExprArgs {
|
pub fn arguments(p: &mut Parser) -> ExprArgs {
|
||||||
@ -54,9 +53,8 @@ fn argument(p: &mut Parser) -> Option<Argument> {
|
|||||||
let first = p.span_if(expr)?;
|
let first = p.span_if(expr)?;
|
||||||
if p.eat_if(Token::Colon) {
|
if p.eat_if(Token::Colon) {
|
||||||
if let Expr::Ident(ident) = first.v {
|
if let Expr::Ident(ident) = first.v {
|
||||||
let expr = p.span_if(expr)?;
|
|
||||||
let name = ident.with_span(first.span);
|
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 }))
|
Some(Argument::Named(Named { name, expr }))
|
||||||
} else {
|
} else {
|
||||||
p.diag(error!(first.span, "expected identifier"));
|
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()? {
|
let node = match p.peek()? {
|
||||||
// Bracket call.
|
// Bracket call.
|
||||||
Token::LeftBracket => {
|
Token::LeftBracket => {
|
||||||
return Some(Node::Expr(bracket_call(p)));
|
return Some(Node::Expr(bracket_call(p)?));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code block.
|
// Code block.
|
||||||
@ -153,22 +153,30 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a bracketed function call.
|
/// 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);
|
p.start_group(Group::Bracket, TokenMode::Code);
|
||||||
|
|
||||||
// One header is guaranteed, but there may be more (through chaining).
|
// One header is guaranteed, but there may be more (through chaining).
|
||||||
let mut outer = vec![];
|
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) {
|
while p.eat_if(Token::Pipe) {
|
||||||
outer.push(inner);
|
if let Some(new) = p.span_if(bracket_subheader) {
|
||||||
inner = p.span(bracket_subheader);
|
outer.extend(inner);
|
||||||
|
inner = Some(new);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.end_group();
|
p.end_group();
|
||||||
|
|
||||||
if p.peek() == Some(Token::LeftBracket) {
|
let body = if p.peek() == Some(Token::LeftBracket) {
|
||||||
let body = p.span(|p| Expr::Template(bracket_body(p)));
|
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.span.expand(body.span);
|
||||||
inner.v.args.v.push(Argument::Pos(body));
|
inner.v.args.v.push(Argument::Pos(body));
|
||||||
}
|
}
|
||||||
@ -181,28 +189,25 @@ fn bracket_call(p: &mut Parser) -> Expr {
|
|||||||
inner = top;
|
inner = top;
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::Call(inner.v)
|
Some(Expr::Call(inner.v))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse one subheader of a bracketed function call.
|
/// 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);
|
p.start_group(Group::Subheader, TokenMode::Code);
|
||||||
|
|
||||||
let start = p.next_start();
|
let name = p.span_if(ident);
|
||||||
let name = p.span_if(ident).unwrap_or_else(|| {
|
if name.is_none() {
|
||||||
let what = "function name";
|
p.expected("function name");
|
||||||
if p.eof() {
|
}
|
||||||
p.expected_at(what, start);
|
|
||||||
} else {
|
|
||||||
p.expected(what);
|
|
||||||
}
|
|
||||||
Ident(String::new()).with_span(start)
|
|
||||||
});
|
|
||||||
|
|
||||||
let args = p.span(arguments);
|
let args = p.span(arguments);
|
||||||
p.end_group();
|
p.end_group();
|
||||||
|
|
||||||
ExprCall { name, args }
|
Some(ExprCall {
|
||||||
|
callee: Box::new(name?.map(Expr::Ident)),
|
||||||
|
args,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the body of a bracketed function call.
|
/// Parse the body of a bracketed function call.
|
||||||
@ -213,78 +218,66 @@ fn bracket_body(p: &mut Parser) -> Tree {
|
|||||||
tree
|
tree
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a block expression: `{...}`.
|
/// Parse an identifier.
|
||||||
fn block(p: &mut Parser) -> Option<Expr> {
|
fn ident(p: &mut Parser) -> Option<Ident> {
|
||||||
p.start_group(Group::Brace, TokenMode::Code);
|
p.eat_map(|token| match token {
|
||||||
let expr = p.span_if(expr);
|
Token::Ident(id) => Some(Ident(id.into())),
|
||||||
while !p.eof() {
|
_ => None,
|
||||||
p.unexpected();
|
})
|
||||||
}
|
|
||||||
p.end_group();
|
|
||||||
Some(Expr::Block(Box::new(expr?)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an expression: `term (+ term)*`.
|
/// Parse an expression.
|
||||||
fn expr(p: &mut Parser) -> Option<Expr> {
|
fn expr(p: &mut Parser) -> Option<Expr> {
|
||||||
binops(p, term, |token| match token {
|
expr_with(p, 0)
|
||||||
Token::Plus => Some(BinOp::Add),
|
|
||||||
Token::Hyph => Some(BinOp::Sub),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a term: `factor (* factor)*`.
|
/// Parse an expression with operators having at least the minimum precedence.
|
||||||
fn term(p: &mut Parser) -> Option<Expr> {
|
fn expr_with(p: &mut Parser, min_prec: usize) -> Option<Expr> {
|
||||||
binops(p, factor, |token| match token {
|
let mut lhs = match p.span_if(|p| p.eat_map(UnOp::from_token)) {
|
||||||
Token::Star => Some(BinOp::Mul),
|
Some(op) => {
|
||||||
Token::Slash => Some(BinOp::Div),
|
let prec = op.v.precedence();
|
||||||
_ => None,
|
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)*`.
|
loop {
|
||||||
fn binops(
|
let op = match p.peek().and_then(BinOp::from_token) {
|
||||||
p: &mut Parser,
|
Some(binop) => binop,
|
||||||
operand: fn(&mut Parser) -> Option<Expr>,
|
None => break,
|
||||||
op: fn(Token) -> Option<BinOp>,
|
};
|
||||||
) -> Option<Expr> {
|
|
||||||
let mut lhs = p.span_if(operand)?;
|
|
||||||
|
|
||||||
while let Some(op) = p.span_if(|p| p.eat_map(op)) {
|
let mut prec = op.precedence();
|
||||||
if let Some(rhs) = p.span_if(operand) {
|
if prec < min_prec {
|
||||||
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 {
|
|
||||||
break;
|
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)
|
Some(lhs.v)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a factor of the form `-?value`.
|
/// Parse a primary expression.
|
||||||
fn factor(p: &mut Parser) -> Option<Expr> {
|
fn primary(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> {
|
|
||||||
let expr = match p.peek() {
|
let expr = match p.peek() {
|
||||||
// Template.
|
// Template.
|
||||||
Some(Token::LeftBracket) => {
|
Some(Token::LeftBracket) => {
|
||||||
@ -342,19 +335,25 @@ fn template(p: &mut Parser) -> Expr {
|
|||||||
Expr::Template(tree)
|
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.
|
/// Parse a parenthesized function call.
|
||||||
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Expr {
|
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Expr {
|
||||||
p.start_group(Group::Paren, TokenMode::Code);
|
p.start_group(Group::Paren, TokenMode::Code);
|
||||||
let args = p.span(arguments);
|
let args = p.span(arguments);
|
||||||
p.end_group();
|
p.end_group();
|
||||||
Expr::Call(ExprCall { name, args })
|
Expr::Call(ExprCall {
|
||||||
}
|
callee: Box::new(name.map(Expr::Ident)),
|
||||||
|
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,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,14 +387,14 @@ fn stmt_let(p: &mut Parser) -> Option<Expr> {
|
|||||||
if p.eat_if(Token::Eq) {
|
if p.eat_if(Token::Eq) {
|
||||||
rhs = p.span_if(expr);
|
rhs = p.span_if(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !p.eof() {
|
||||||
|
p.expected_at("semicolon or line break", p.last_end());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
p.expected("identifier");
|
p.expected("identifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.eof() {
|
|
||||||
p.expected_at("semicolon or line break", p.last_end());
|
|
||||||
}
|
|
||||||
|
|
||||||
p.end_group();
|
p.end_group();
|
||||||
|
|
||||||
Some(Expr::Let(ExprLet { pat: pat?, expr: rhs.map(Box::new) }))
|
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!]`.
|
/// A template expression: `[*Hi* there!]`.
|
||||||
pub type ExprTemplate = Tree;
|
pub type ExprTemplate = Tree;
|
||||||
|
|
||||||
@ -127,18 +144,246 @@ pub type ExprGroup = Box<Spanned<Expr>>;
|
|||||||
/// A block expression: `{1 + 2}`.
|
/// A block expression: `{1 + 2}`.
|
||||||
pub type ExprBlock = Box<Spanned<Expr>>;
|
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(...)`.
|
/// An invocation of a function: `[foo ...]`, `foo(...)`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ExprCall {
|
pub struct ExprCall {
|
||||||
/// The name of the function.
|
/// The callee of the function.
|
||||||
pub name: Spanned<Ident>,
|
pub callee: Box<Spanned<Expr>>,
|
||||||
/// The arguments to the function.
|
/// The arguments to the function.
|
||||||
pub args: Spanned<ExprArgs>,
|
pub args: Spanned<ExprArgs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pretty for ExprCall {
|
impl Pretty for ExprCall {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
p.push_str(&self.name.v);
|
self.callee.v.pretty(p);
|
||||||
p.push_str("(");
|
p.push_str("(");
|
||||||
self.args.v.pretty(p);
|
self.args.v.pretty(p);
|
||||||
p.push_str(")");
|
p.push_str(")");
|
||||||
@ -154,7 +399,7 @@ pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function name.
|
// 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.
|
// 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`.
|
/// A let expression: `let x = 1`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ExprLet {
|
pub struct ExprLet {
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
|
use std::cell::Cell;
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
use std::cell::Cell;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static CMP_SPANS: Cell<bool> = Cell::new(true);
|
static CMP_SPANS: Cell<bool> = Cell::new(true);
|
||||||
}
|
}
|
||||||
@ -148,10 +145,27 @@ impl Span {
|
|||||||
self.start.to_usize() .. self.end.to_usize()
|
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.
|
/// When set to `false` comparisons with `PartialEq` ignore spans.
|
||||||
#[cfg(test)]
|
fn set_cmp(cmp: bool) {
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) fn set_cmp(cmp: bool) {
|
|
||||||
CMP_SPANS.with(|cell| cell.set(cmp));
|
CMP_SPANS.with(|cell| cell.set(cmp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,12 +183,7 @@ impl Eq for Span {}
|
|||||||
|
|
||||||
impl PartialEq for Span {
|
impl PartialEq for Span {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
#[cfg(test)]
|
!Self::cmp() || (self.start == other.start && self.end == other.end)
|
||||||
if !CMP_SPANS.with(Cell::get) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
[f|f|f]
|
||||||
|
|
||||||
// With body.
|
// 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
|
// Error: 2:2-2:2 expected function name
|
||||||
[|f true]
|
// Error: 1:3-1:3 expected function name
|
||||||
|
[||f true]
|
||||||
|
|
||||||
// Error: 1:6-1:6 expected function name
|
// Error: 1:6-1:6 expected function name
|
||||||
[f 1|]
|
[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: 2:5-2:5 expected closing paren
|
||||||
// Error: 1:8-1:9 expected expression, found closing paren
|
// Error: 1:8-1:9 expected expression, found closing paren
|
||||||
[f (|f )]
|
[f (|f )]
|
||||||
|
@ -3,44 +3,118 @@
|
|||||||
#let a = 2
|
#let a = 2
|
||||||
#let b = 4
|
#let b = 4
|
||||||
|
|
||||||
|
// Error: 1:14-1:17 cannot apply '+' to string
|
||||||
|
#let error = +""
|
||||||
|
|
||||||
// Paren call.
|
// Paren call.
|
||||||
[eq f(1), "f(1)"]
|
[test f(1), "f(1)"]
|
||||||
[eq type(1), "integer"]
|
[test type(1), "integer"]
|
||||||
|
|
||||||
// Unary operations.
|
// Unary operations.
|
||||||
[eq +1, 1]
|
[test +1, 1]
|
||||||
[eq -1, 1-2]
|
[test -1, 1-2]
|
||||||
[eq --1, 1]
|
[test --1, 1]
|
||||||
|
|
||||||
// Binary operations.
|
// Math operations.
|
||||||
[eq "a" + "b", "ab"]
|
[test "a" + "b", "ab"]
|
||||||
[eq 1-4, 3*-1]
|
[test 1-4, 3*-1]
|
||||||
[eq a * b, 8]
|
[test a * b, 8]
|
||||||
[eq 12pt/.4, 30pt]
|
[test 12pt/.4, 30pt]
|
||||||
|
[test 1e+2-1e-2, 99.99]
|
||||||
|
|
||||||
// Associativity.
|
// Associativity.
|
||||||
[eq 1+2+3, 6]
|
[test 1+2+3, 6]
|
||||||
[eq 1/2*3, 1.5]
|
[test 1/2*3, 1.5]
|
||||||
|
|
||||||
// Precedence.
|
// 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.
|
// Parentheses.
|
||||||
[eq (a), 2]
|
[test (a), 2]
|
||||||
[eq (2), 2]
|
[test (2), 2]
|
||||||
[eq (1+2)*3, 9]
|
[test (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]
|
|
||||||
|
|
||||||
// Error: 1:3-1:3 expected expression
|
// Error: 1:3-1:3 expected expression
|
||||||
{-}
|
{-}
|
||||||
|
|
||||||
// Error: 1:8-1:8 expected expression
|
// Error: 1:10-1:10 expected expression
|
||||||
[eq {1+}, 1]
|
[test {1+}, 1]
|
||||||
|
|
||||||
// Error: 1:8-1:8 expected expression
|
// Error: 1:10-1:10 expected expression
|
||||||
[eq {2*}, 2]
|
[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`.
|
// Automatically initialized with `none`.
|
||||||
#let x
|
#let x
|
||||||
[eq x, none]
|
[test x, none]
|
||||||
|
|
||||||
// Initialized with `1`.
|
// Initialized with `1`.
|
||||||
#let y = 1
|
#let y = 1
|
||||||
[eq y, 1]
|
[test y, 1]
|
||||||
|
|
||||||
// Initialize with template, not terminated by semicolon in template.
|
// Initialize with template, not terminated by semicolon in template.
|
||||||
#let v = [Hello; there]
|
#let v = [Hello; there]
|
||||||
@ -15,15 +15,19 @@
|
|||||||
2,
|
2,
|
||||||
3,
|
3,
|
||||||
)
|
)
|
||||||
[eq x, (1, 2, 3)]
|
[test x, (1, 2, 3)]
|
||||||
|
|
||||||
// Multiple bindings in one line.
|
// 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.
|
// Invalid name.
|
||||||
// Error: 1:6-1:7 expected identifier, found integer
|
// Error: 1:6-1:7 expected identifier, found integer
|
||||||
#let 1
|
#let 1
|
||||||
|
|
||||||
|
// Invalid name.
|
||||||
|
// Error: 1:6-1:7 expected identifier, found integer
|
||||||
|
#let 1 = 2
|
||||||
|
|
||||||
// Terminated by end of line before binding name.
|
// Terminated by end of line before binding name.
|
||||||
// Error: 1:5-1:5 expected identifier
|
// Error: 1:5-1:5 expected identifier
|
||||||
#let
|
#let
|
||||||
@ -35,24 +39,24 @@ The Fi#let;rst
|
|||||||
|
|
||||||
// Terminated with just a line break.
|
// Terminated with just a line break.
|
||||||
#let v = "a"
|
#let v = "a"
|
||||||
The Second [eq v, "a"]
|
The Second [test v, "a"]
|
||||||
|
|
||||||
// Terminated with semicolon + line break.
|
// Terminated with semicolon + line break.
|
||||||
#let v = "a";
|
#let v = "a";
|
||||||
The Third [eq v, "a"]
|
The Third [test v, "a"]
|
||||||
|
|
||||||
// Terminated with just a semicolon.
|
// 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.
|
// Terminated by semicolon even though we are in a paren group.
|
||||||
// Error: 2:25-2:25 expected expression
|
// Error: 2:25-2:25 expected expression
|
||||||
// Error: 1:25-1:25 expected closing paren
|
// 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.
|
// Not terminated.
|
||||||
// Error: 1:16-1:16 expected semicolon or line break
|
// 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.
|
// Not terminated.
|
||||||
// Error: 1:16-1:16 expected semicolon or line break
|
// 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 ok = true;
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
|
|
||||||
|
let mut lines = 0;
|
||||||
for (i, part) in src.split("---").enumerate() {
|
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;
|
ok &= part_ok;
|
||||||
frames.extend(part_frames);
|
frames.extend(part_frames);
|
||||||
|
lines += part.lines().count() as u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !frames.is_empty() {
|
if !frames.is_empty() {
|
||||||
@ -177,7 +179,7 @@ fn test(
|
|||||||
ok
|
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 map = LineMap::new(src);
|
||||||
let (compare_ref, ref_diags) = parse_metadata(src, &map);
|
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 {
|
for diag in &diags {
|
||||||
if !ref_diags.contains(diag) {
|
if !ref_diags.contains(diag) {
|
||||||
print!(" Not annotated | ");
|
print!(" Not annotated | ");
|
||||||
print_diag(diag, &map);
|
print_diag(diag, &map, lines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for diag in &ref_diags {
|
for diag in &ref_diags {
|
||||||
if !diags.contains(diag) {
|
if !diags.contains(diag) {
|
||||||
print!(" Not emitted | ");
|
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())
|
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 lhs = args.require::<Value>(ctx, "left-hand side");
|
||||||
let rhs = args.require::<Value>(ctx, "right-hand side");
|
let rhs = args.require::<Value>(ctx, "right-hand side");
|
||||||
if lhs != rhs {
|
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.define("f", ValueFunc::new("f", f));
|
||||||
scope.set("eq", ValueFunc::new("eq", eq));
|
scope.define("test", ValueFunc::new("test", test));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_diag(diag: &Spanned<Diag>, map: &LineMap) {
|
fn print_diag(diag: &Spanned<Diag>, map: &LineMap, lines: u32) {
|
||||||
let start = map.location(diag.span.start).unwrap();
|
let mut start = map.location(diag.span.start).unwrap();
|
||||||
let end = map.location(diag.span.end).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);
|
println!("{}: {}-{}: {}", diag.v.level, start, end, diag.v.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user