Group and block expressions, unary plus 🧩

This commit is contained in:
Laurenz 2021-01-12 18:55:12 +01:00
parent 105cda0e69
commit d2ba1b705e
8 changed files with 398 additions and 342 deletions

View File

@ -4,6 +4,7 @@
mod value;
mod call;
mod context;
mod ops;
mod scope;
mod state;
@ -45,17 +46,6 @@ pub trait Eval {
fn eval(self, ctx: &mut EvalContext) -> Self::Output;
}
impl<'a, T> Eval for &'a Box<Spanned<T>>
where
Spanned<&'a T>: Eval,
{
type Output = <Spanned<&'a T> as Eval>::Output;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
(**self).as_ref().eval(ctx)
}
}
impl Eval for &[Spanned<Node>] {
type Output = ();
@ -171,6 +161,8 @@ impl Eval for Spanned<&Expr> {
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::Template(v) => Value::Template(v.clone()),
Expr::Group(v) => v.as_ref().with_span(self.span).eval(ctx),
Expr::Block(v) => v.as_ref().with_span(self.span).eval(ctx),
}
}
}
@ -179,7 +171,7 @@ impl Eval for Spanned<&ExprUnary> {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let value = self.v.expr.eval(ctx);
let value = (*self.v.expr).as_ref().eval(ctx);
if let Value::Error = value {
return Value::Error;
@ -187,7 +179,8 @@ impl Eval for Spanned<&ExprUnary> {
let span = self.v.op.span.join(self.v.expr.span);
match self.v.op.v {
UnOp::Neg => neg(ctx, span, value),
UnOp::Pos => ops::pos(ctx, span, value),
UnOp::Neg => ops::neg(ctx, span, value),
}
}
}
@ -196,8 +189,8 @@ impl Eval for Spanned<&ExprBinary> {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let lhs = self.v.lhs.eval(ctx);
let rhs = self.v.rhs.eval(ctx);
let lhs = (*self.v.lhs).as_ref().eval(ctx);
let rhs = (*self.v.rhs).as_ref().eval(ctx);
if lhs == Value::Error || rhs == Value::Error {
return Value::Error;
@ -205,10 +198,10 @@ impl Eval for Spanned<&ExprBinary> {
let span = self.v.lhs.span.join(self.v.rhs.span);
match self.v.op.v {
BinOp::Add => add(ctx, span, lhs, rhs),
BinOp::Sub => sub(ctx, span, lhs, rhs),
BinOp::Mul => mul(ctx, span, lhs, rhs),
BinOp::Div => div(ctx, span, lhs, rhs),
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),
}
}
}
@ -231,172 +224,3 @@ impl Eval for Spanned<&ExprDict> {
.collect()
}
}
/// Compute the negation of a value.
fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
use Value::*;
match value {
Int(v) => Int(-v),
Float(v) => Float(-v),
Length(v) => Length(-v),
Relative(v) => Relative(-v),
Linear(v) => Linear(-v),
v => {
ctx.diag(error!(span, "cannot negate {}", v.type_name()));
Value::Error
}
}
}
/// Compute the sum of two values.
fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use Value::*;
match (lhs, rhs) {
// Numbers 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),
(Float(a), Float(b)) => Float(a + b),
// Lengths, relatives and linears to themselves.
(Length(a), Length(b)) => Length(a + b),
(Length(a), Relative(b)) => Linear(a + b),
(Length(a), Linear(b)) => Linear(a + b),
(Relative(a), Length(b)) => Linear(a + b),
(Relative(a), Relative(b)) => Relative(a + b),
(Relative(a), Linear(b)) => Linear(a + b),
(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
}
}
}
/// Compute the difference of two values.
fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use 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),
(Float(a), Float(b)) => Float(a - b),
// Lengths, relatives and linears from themselves.
(Length(a), Length(b)) => Length(a - b),
(Length(a), Relative(b)) => Linear(a - b),
(Length(a), Linear(b)) => Linear(a - b),
(Relative(a), Length(b)) => Linear(a - b),
(Relative(a), Relative(b)) => Relative(a - b),
(Relative(a), Linear(b)) => Linear(a - b),
(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
}
}
}
/// Compute the product of two values.
fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use Value::*;
match (lhs, rhs) {
// Numbers with 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),
(Float(a), Float(b)) => Float(a * b),
// Lengths, relatives and linears with numbers.
(Length(a), Int(b)) => Length(a * b as f64),
(Length(a), Float(b)) => Length(a * b),
(Int(a), Length(b)) => Length(a as f64 * b),
(Float(a), Length(b)) => Length(a * b),
(Relative(a), Int(b)) => Relative(a * b as f64),
(Relative(a), Float(b)) => Relative(a * b),
(Int(a), Relative(b)) => Relative(a as f64 * b),
(Float(a), Relative(b)) => Relative(a * b),
(Linear(a), Int(b)) => Linear(a * b as f64),
(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
}
}
}
/// Compute the quotient of two values.
fn div(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use Value::*;
match (lhs, rhs) {
// Numbers by themselves.
(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),
(Float(a), Float(b)) => Float(a / b),
// Lengths by numbers.
(Length(a), Int(b)) => Length(a / b as f64),
(Length(a), Float(b)) => Length(a / b),
(Relative(a), Int(b)) => Relative(a / b as f64),
(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
}
}
}
/// Concatenate two collections.
fn concat<T, A>(mut a: T, b: T) -> T
where
T: Extend<A> + IntoIterator<Item = A>,
{
a.extend(b);
a
}

183
src/eval/ops.rs Normal file
View File

@ -0,0 +1,183 @@
use super::*;
/// 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
}
}
/// Compute the negation of a value.
pub fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
use 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),
v => {
ctx.diag(error!(span, "cannot negate {}", v.type_name()));
Value::Error
}
}
}
/// Compute the sum of two values.
pub fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use 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),
(Float(a), Float(b)) => Float(a + b),
(Angle(a), Angle(b)) => Angle(a + b),
(Length(a), Length(b)) => Length(a + b),
(Length(a), Relative(b)) => Linear(a + b),
(Length(a), Linear(b)) => Linear(a + b),
(Relative(a), Length(b)) => Linear(a + b),
(Relative(a), Relative(b)) => Relative(a + b),
(Relative(a), Linear(b)) => Linear(a + b),
(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
}
}
}
/// Compute the difference of two values.
pub fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use 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),
(Float(a), Float(b)) => Float(a - b),
(Angle(a), Angle(b)) => Angle(a - b),
(Length(a), Length(b)) => Length(a - b),
(Length(a), Relative(b)) => Linear(a - b),
(Length(a), Linear(b)) => Linear(a - b),
(Relative(a), Length(b)) => Linear(a - b),
(Relative(a), Relative(b)) => Relative(a - b),
(Relative(a), Linear(b)) => Linear(a - b),
(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
}
}
}
/// Compute the product of two values.
pub fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use 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),
(Float(a), Float(b)) => Float(a * b),
(Length(a), Int(b)) => Length(a * b as f64),
(Length(a), Float(b)) => Length(a * b),
(Int(a), Length(b)) => Length(a as f64 * b),
(Float(a), Length(b)) => Length(a * b),
(Angle(a), Int(b)) => Angle(a * b as f64),
(Angle(a), Float(b)) => Angle(a * b),
(Int(a), Angle(b)) => Angle(a as f64 * b),
(Float(a), Angle(b)) => Angle(a * b),
(Relative(a), Int(b)) => Relative(a * b as f64),
(Relative(a), Float(b)) => Relative(a * b),
(Int(a), Relative(b)) => Relative(a as f64 * b),
(Float(a), Relative(b)) => Relative(a * b),
(Linear(a), Int(b)) => Linear(a * b as f64),
(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
}
}
}
/// Compute the quotient of two values.
pub fn div(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use 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),
(Float(a), Float(b)) => Float(a / b),
(Length(a), Int(b)) => Length(a / b as f64),
(Length(a), Float(b)) => Length(a / b),
(Angle(a), Int(b)) => Angle(a / b as f64),
(Angle(a), Float(b)) => Angle(a / b),
(Relative(a), Int(b)) => Relative(a / b as f64),
(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
}
}
}
/// Concatenate two collections.
fn concat<T, A>(mut a: T, b: T) -> T
where
T: Extend<A> + IntoIterator<Item = A>,
{
a.extend(b);
a
}

View File

@ -8,7 +8,7 @@ use super::{Args, Eval, EvalContext};
use crate::color::Color;
use crate::geom::{Angle, Length, Linear, Relative};
use crate::pretty::{pretty, Pretty, Printer};
use crate::syntax::{pretty_template_expr, Spanned, Tree, WithSpan};
use crate::syntax::{Spanned, Tree, WithSpan};
/// A computational value.
#[derive(Debug, Clone, PartialEq)]
@ -77,6 +77,18 @@ 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 {
@ -112,9 +124,13 @@ impl Pretty for Value {
Value::Linear(v) => write!(p, "{}", v).unwrap(),
Value::Color(v) => write!(p, "{}", v).unwrap(),
Value::Str(v) => write!(p, "{:?}", v).unwrap(),
Value::Array(array) => array.pretty(p),
Value::Dict(dict) => dict.pretty(p),
Value::Template(template) => pretty_template_expr(template, p),
Value::Array(v) => v.pretty(p),
Value::Dict(v) => v.pretty(p),
Value::Template(v) => {
p.push_str("[");
v.pretty(p);
p.push_str("]");
}
Value::Func(v) => v.pretty(p),
Value::Any(v) => v.pretty(p),
Value::Error => p.push_str("(error)"),

View File

@ -59,7 +59,7 @@ fn argument(p: &mut Parser) -> Option<Argument> {
p.deco(Deco::Name.with_span(name.span));
Some(Argument::Named(Named { name, expr }))
} else {
p.diag(error!(first.span, "name must be identifier"));
p.diag(error!(first.span, "expected identifier"));
expr(p);
None
}
@ -93,7 +93,7 @@ impl State {
fn into_expr(self) -> Expr {
match self {
Self::Unknown => Expr::Array(vec![]),
Self::Expr(expr) => expr.v,
Self::Expr(expr) => Expr::Group(Box::new(expr.v)),
Self::Array(array) => Expr::Array(array),
Self::Dict(dict) => Expr::Dict(dict),
}

View File

@ -51,12 +51,12 @@ fn node(p: &mut Parser, at_start: bool) -> Option<Node> {
let node = match p.peek()? {
// Bracket call.
Token::LeftBracket => {
return Some(Node::Expr(Expr::Call(bracket_call(p))));
return Some(Node::Expr(bracket_call(p)));
}
// Code block.
Token::LeftBrace => {
return Some(Node::Expr(code_block(p)?));
return Some(Node::Expr(block(p)?));
}
// Markup.
@ -154,7 +154,7 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String {
}
/// Parse a bracketed function call.
fn bracket_call(p: &mut Parser) -> ExprCall {
fn bracket_call(p: &mut Parser) -> Expr {
p.push_mode(TokenMode::Code);
p.start_group(Group::Bracket);
@ -184,7 +184,7 @@ fn bracket_call(p: &mut Parser) -> ExprCall {
inner = top;
}
inner.v
Expr::Call(inner.v)
}
/// Parse one subheader of a bracketed function call.
@ -218,8 +218,8 @@ fn bracket_body(p: &mut Parser) -> Tree {
tree
}
/// Parse a code block: `{...}`.
fn code_block(p: &mut Parser) -> Option<Expr> {
/// Parse a block expression: `{...}`.
fn block(p: &mut Parser) -> Option<Expr> {
p.push_mode(TokenMode::Code);
p.start_group(Group::Brace);
let expr = expr(p);
@ -228,7 +228,7 @@ fn code_block(p: &mut Parser) -> Option<Expr> {
}
p.pop_mode();
p.end_group();
expr
Some(Expr::Block(Box::new(expr?)))
}
/// Parse an expression: `term (+ term)*`.
@ -277,6 +277,7 @@ fn binops(
/// 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,
};
@ -294,12 +295,12 @@ fn value(p: &mut Parser) -> Option<Expr> {
let expr = match p.peek() {
// Template.
Some(Token::LeftBracket) => {
return Some(Expr::Template(template(p)));
return Some(template(p));
}
// Nested block.
Some(Token::LeftBrace) => {
return code_block(p);
return block(p);
}
// Dictionary or just a parenthesized expression.
@ -313,7 +314,7 @@ fn value(p: &mut Parser) -> Option<Expr> {
let ident = Ident(id.into());
if p.peek() == Some(Token::LeftParen) {
let name = ident.with_span(p.peek_span());
return Some(Expr::Call(paren_call(p, name)));
return Some(paren_call(p, name));
} else {
return Some(Expr::Ident(ident));
}
@ -341,21 +342,21 @@ fn value(p: &mut Parser) -> Option<Expr> {
}
// Parse a template value: `[...]`.
fn template(p: &mut Parser) -> Tree {
fn template(p: &mut Parser) -> Expr {
p.push_mode(TokenMode::Markup);
p.start_group(Group::Bracket);
let tree = tree(p);
p.pop_mode();
p.end_group();
tree
Expr::Template(tree)
}
/// Parse a parenthesized function call.
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> ExprCall {
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Expr {
p.start_group(Group::Paren);
let args = p.span(arguments);
p.end_group();
ExprCall { name, args }
Expr::Call(ExprCall { name, args })
}
/// Parse an identifier.

View File

@ -10,8 +10,8 @@ use crate::syntax::*;
use BinOp::*;
use Expr::{Angle, Bool, Color, Float, Int, Length, Percent};
use Node::{Emph, Expr as Block, Linebreak, Parbreak, Space, Strong};
use UnOp::*;
use Node::{Emph, Linebreak, Parbreak, Space, Strong};
use UnOp::{Neg, Pos};
macro_rules! t {
($src:literal
@ -126,21 +126,26 @@ fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr {
})
}
macro_rules! Array {
(@$($expr:expr),* $(,)?) => {
vec![$(into!($expr)),*]
};
($($tts:tt)*) => (Expr::Array(Array![@$($tts)*]));
fn Group(expr: Expr) -> Expr {
Expr::Group(Box::new(expr))
}
macro_rules! Dict {
(@$($name:expr => $expr:expr),* $(,)?) => {
vec![$(Named {
name: into!($name).map(|s: &str| Ident(s.into())),
expr: into!($expr)
}),*]
macro_rules! Call {
(@@$name:expr) => {
Call!(@@$name, Args![])
};
(@@$name:expr, $args:expr) => {
ExprCall {
name: into!($name).map(|s: &str| Ident(s.into())),
args: into!($args),
}
};
(@$($tts:tt)*) => {
Expr::Call(Call!(@@$($tts)*))
};
($($tts:tt)*) => {
Node::Expr(Call!(@$($tts)*))
};
($($tts:tt)*) => (Expr::Dict(Dict![@$($tts)*]));
}
macro_rules! Args {
@ -158,23 +163,43 @@ macro_rules! Args {
};
}
macro_rules! Template {
(@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]);
($($tts:tt)*) => (Expr::Template(Template![@$($tts)*]));
macro_rules! Array {
(@$($expr:expr),* $(,)?) => {
vec![$(into!($expr)),*]
};
($($tts:tt)*) => {
Expr::Array(Array![@$($tts)*])
};
}
macro_rules! Call {
(@@$name:expr) => {
Call!(@@$name, Args![])
};
(@@$name:expr, $args:expr) => {
ExprCall {
macro_rules! Dict {
(@$($name:expr => $expr:expr),* $(,)?) => {
vec![$(Named {
name: into!($name).map(|s: &str| Ident(s.into())),
args: into!($args),
}
expr: into!($expr)
}),*]
};
($($tts:tt)*) => {
Expr::Dict(Dict![@$($tts)*])
};
}
macro_rules! Template {
(@$($node:expr),* $(,)?) => {
vec![$(into!($node)),*]
};
($($tts:tt)*) => {
Expr::Template(Template![@$($tts)*])
};
}
macro_rules! Block {
(@$expr:expr) => {
Expr::Block(Box::new($expr))
};
($expr:expr) => {
Node::Expr(Block!(@$expr))
};
(@$($tts:tt)*) => (Expr::Call(Call!(@@$($tts)*)));
($($tts:tt)*) => (Node::Expr(Call!(@$($tts)*)));
}
#[test]
@ -247,7 +272,7 @@ fn test_parse_headings() {
// Continued heading.
t!("# a{\n1\n}b" Heading(0, Template![
@Space, Text("a"), Block(Int(1)), Text("b")
@Space, Text("a"), Block!(Int(1)), Text("b")
]));
t!("# a[f][\n\n]d" Heading(0, Template![@
Space, Text("a"), Call!("f", Args![Template![Parbreak]]), Text("d"),
@ -294,7 +319,7 @@ fn test_parse_escape_sequences() {
fn test_parse_groups() {
// Test paren group.
t!("{({1) + 3}"
nodes: [Block(Binary(Int(1), Add, Int(3)))],
nodes: [Block!(Binary(Group(Block!(@Int(1))), Add, Int(3)))],
errors: [S(4..4, "expected closing brace")]);
// Test bracket group.
@ -309,7 +334,7 @@ fn test_parse_groups() {
// Test brace group.
t!("{1 + [}"
nodes: [Block(Binary(Int(1), Add, Template![]))],
nodes: [Block!(Binary(Int(1), Add, Template![]))],
errors: [S(6..6, "expected closing bracket")]);
// Test subheader group.
@ -322,11 +347,11 @@ fn test_parse_groups() {
#[test]
fn test_parse_blocks() {
// Basic with spans.
t!("{1}" nodes: [S(0..3, Block(Int(1)))], spans: true);
t!("{1}" nodes: [S(0..3, Block!(Int(1)))], spans: true);
// Function calls.
t!("{f()}" Call!("f"));
t!("{[[f]]}" Block(Template![Call!("f")]));
t!("{f()}" Block!(Call!(@"f")));
t!("{[[f]]}" Block!(Template![Call!("f")]));
// Missing or bad value.
t!("{}{1u}"
@ -336,7 +361,7 @@ fn test_parse_blocks() {
// Too much stuff.
t!("{1 #{} end"
nodes: [Block(Int(1)), Space, Text("end")],
nodes: [Block!(Int(1)), Space, Text("end")],
errors: [S(3..4, "unexpected hex value"),
S(4..5, "unexpected opening brace")]);
}
@ -424,7 +449,7 @@ fn test_parse_arguments() {
t!("[v a:2]" Call!("v", Args!["a" => Int(2)]));
// Parenthesized function with nested array literal.
t!(r#"{f(1, a: (2, 3), #004, b: "five")}"# Block(Call!(@"f", Args![
t!(r#"{f(1, a: (2, 3), #004, b: "five")}"# Block!(Call!(@"f", Args![
Int(1),
"a" => Array![Int(2), Int(3)],
Color(RgbaColor::new(0, 0, 0x44, 0xff)),
@ -449,30 +474,35 @@ fn test_parse_arguments() {
// Name has to be identifier.
t!("[v 1:]"
nodes: [Call!("v", Args![])],
errors: [S(3..4, "name must be identifier"),
errors: [S(3..4, "expected identifier"),
S(5..5, "expected expression")]);
// Name has to be identifier.
t!("[v 1:2]"
nodes: [Call!("v", Args![])],
errors: [S(3..4, "name must be identifier")]);
errors: [S(3..4, "expected identifier")]);
// Name has to be identifier.
t!("[v (x):1]"
nodes: [Call!("v", Args![])],
errors: [S(3..6, "expected identifier")]);
}
#[test]
fn test_parse_arrays() {
// Empty array.
t!("{()}" Block(Array![]));
t!("{()}" Block!(Array![]));
// Array with one item and trailing comma + spans.
t!("{-(1,)}"
nodes: [S(0..7, Block(Unary(
nodes: [S(0..7, Block!(Unary(
S(1..2, Neg),
S(2..6, Array![S(3..4, Int(1))])
)))],
spans: true);
// Array with three items and trailing comma.
t!(r#"{("one", 2, #003,)}"# Block(Array![
t!(r#"{("one", 2, #003,)}"# Block!(Array![
Str("one"),
Int(2),
Color(RgbaColor::new(0, 0, 0x33, 0xff))
@ -480,44 +510,44 @@ fn test_parse_arrays() {
// Unclosed.
t!("{(}"
nodes: [Block(Array![])],
nodes: [Block!(Array![])],
errors: [S(2..2, "expected closing paren")]);
// Missing comma + invalid token.
t!("{(1*/2)}"
nodes: [Block(Array![Int(1), Int(2)])],
nodes: [Block!(Array![Int(1), Int(2)])],
errors: [S(3..5, "expected expression, found end of block comment"),
S(3..3, "expected comma")]);
// Invalid token.
t!("{(1, 1u 2)}"
nodes: [Block(Array![Int(1), Int(2)])],
nodes: [Block!(Array![Int(1), Int(2)])],
errors: [S(5..7, "expected expression, found invalid token")]);
// Coerced to expression with leading comma.
t!("{(,1)}"
nodes: [Block(Int(1))],
nodes: [Block!(Group(Int(1)))],
errors: [S(2..3, "expected expression, found comma")]);
// Missing expression after name makes this an array.
t!("{(a:)}"
nodes: [Block(Array![])],
nodes: [Block!(Array![])],
errors: [S(4..4, "expected expression")]);
// Expected expression, found named pair.
t!("{(1, b: 2)}"
nodes: [Block(Array![Int(1)])],
nodes: [Block!(Array![Int(1)])],
errors: [S(5..9, "expected expression, found named pair")]);
}
#[test]
fn test_parse_dictionaries() {
// Empty dictionary.
t!("{(:)}" Block(Dict![]));
t!("{(:)}" Block!(Dict![]));
// Dictionary with two pairs + spans.
t!("{(one: 1, two: 2)}"
nodes: [S(0..18, Block(Dict![
nodes: [S(0..18, Block!(Dict![
S(2..5, "one") => S(7..8, Int(1)),
S(10..13, "two") => S(15..16, Int(2)),
]))],
@ -525,49 +555,50 @@ fn test_parse_dictionaries() {
// Expected named pair, found expression.
t!("{(a: 1, b)}"
nodes: [Block(Dict!["a" => Int(1)])],
nodes: [Block!(Dict!["a" => Int(1)])],
errors: [S(8..9, "expected named pair, found expression")]);
// Dictionary marker followed by more stuff.
t!("{(:1 b:[], true::)}"
nodes: [Block(Dict!["b" => Template![]])],
nodes: [Block!(Dict!["b" => Template![]])],
errors: [S(3..4, "expected named pair, found expression"),
S(4..4, "expected comma"),
S(11..15, "name must be identifier"),
S(11..15, "expected identifier"),
S(16..17, "expected expression, found colon")]);
}
#[test]
fn test_parse_expressions() {
// Parentheses.
t!("{(x)}{(1)}" Block(Id("x")), Block(Int(1)));
t!("{(x)}{(1)}" Block!(Group(Id("x"))), Block!(Group(Int(1))));
// Unary operations.
t!("{-1}" Block(Unary(Neg, Int(1))));
t!("{--1}" Block(Unary(Neg, Unary(Neg, Int(1)))));
t!("{+1}" Block!(Unary(Pos, Int(1))));
t!("{-1}" Block!(Unary(Neg, Int(1))));
t!("{--1}" Block!(Unary(Neg, Unary(Neg, Int(1)))));
// Binary operations.
t!(r#"{"x"+"y"}"# Block(Binary(Str("x"), Add, Str("y"))));
t!("{1-2}" Block(Binary(Int(1), Sub, Int(2))));
t!("{a * b}" Block(Binary(Id("a"), Mul, Id("b"))));
t!("{12pt/.4}" Block(Binary(Length(12.0, LengthUnit::Pt), Div, Float(0.4))));
t!(r#"{"x"+"y"}"# Block!(Binary(Str("x"), Add, Str("y"))));
t!("{1-2}" Block!(Binary(Int(1), Sub, Int(2))));
t!("{a * b}" Block!(Binary(Id("a"), Mul, Id("b"))));
t!("{12pt/.4}" Block!(Binary(Length(12.0, LengthUnit::Pt), Div, Float(0.4))));
// Associativity.
t!("{1+2+3}" Block(Binary(Binary(Int(1), Add, Int(2)), Add, Int(3))));
t!("{1/2*3}" Block(Binary(Binary(Int(1), Div, Int(2)), Mul, Int(3))));
t!("{1+2+3}" Block!(Binary(Binary(Int(1), Add, Int(2)), Add, Int(3))));
t!("{1/2*3}" Block!(Binary(Binary(Int(1), Div, Int(2)), Mul, Int(3))));
// Precedence.
t!("{1+2*-3}" Block(Binary(
t!("{1+2*-3}" Block!(Binary(
Int(1), Add, Binary(Int(2), Mul, Unary(Neg, Int(3))),
)));
// Confusion with floating-point literal.
t!("{1e-3-4e+4}" Block(Binary(Float(1e-3), Sub, Float(4e+4))));
t!("{1e-3-4e+4}" Block!(Binary(Float(1e-3), Sub, Float(4e+4))));
// Spans + parentheses winning over precedence.
t!("{(1+2)*3}"
nodes: [S(0..9, Block(Binary(
S(1..6, Binary(S(2..3, Int(1)), S(3..4, Add), S(4..5, Int(2)))),
nodes: [S(0..9, Block!(Binary(
S(1..6, Group(Binary(S(2..3, Int(1)), S(3..4, Add), S(4..5, Int(2))))),
S(6..7, Mul),
S(7..8, Int(3)),
)))],
@ -575,7 +606,7 @@ fn test_parse_expressions() {
// Errors.
t!("{-}{1+}{2*}"
nodes: [Block(Int(1)), Block(Int(2))],
nodes: [Block!(Int(1)), Block!(Int(2))],
errors: [S(2..2, "expected expression"),
S(6..6, "expected expression"),
S(10..10, "expected expression")]);
@ -584,37 +615,36 @@ fn test_parse_expressions() {
#[test]
fn test_parse_values() {
// Basics.
t!("{_}" Block(Id("_")));
t!("{name}" Block(Id("name")));
t!("{ke-bab}" Block(Id("ke-bab")));
t!("{α}" Block(Id("α")));
t!("{none}" Block(Expr::None));
t!("{true}" Block(Bool(true)));
t!("{false}" Block(Bool(false)));
t!("{1.0e-4}" Block(Float(1e-4)));
t!("{3.15}" Block(Float(3.15)));
t!("{50%}" Block(Percent(50.0)));
t!("{4.5cm}" Block(Length(4.5, LengthUnit::Cm)));
t!("{12e1pt}" Block(Length(12e1, LengthUnit::Pt)));
t!("{13rad}" Block(Angle(13.0, AngularUnit::Rad)));
t!("{45deg}" Block(Angle(45.0, AngularUnit::Deg)));
t!("{_}" Block!(Id("_")));
t!("{name}" Block!(Id("name")));
t!("{ke-bab}" Block!(Id("ke-bab")));
t!("{α}" Block!(Id("α")));
t!("{none}" Block!(Expr::None));
t!("{true}" Block!(Bool(true)));
t!("{false}" Block!(Bool(false)));
t!("{1.0e-4}" Block!(Float(1e-4)));
t!("{3.15}" Block!(Float(3.15)));
t!("{50%}" Block!(Percent(50.0)));
t!("{4.5cm}" Block!(Length(4.5, LengthUnit::Cm)));
t!("{12e1pt}" Block!(Length(12e1, LengthUnit::Pt)));
t!("{13rad}" Block!(Angle(13.0, AngularUnit::Rad)));
t!("{45deg}" Block!(Angle(45.0, AngularUnit::Deg)));
// Strings.
t!(r#"{"hi"}"# Block(Str("hi")));
t!(r#"{"a\n[]\"\u{1F680}string"}"# Block(Str("a\n[]\"🚀string")));
t!(r#"{"hi"}"# Block!(Str("hi")));
t!(r#"{"a\n[]\"\u{1F680}string"}"# Block!(Str("a\n[]\"🚀string")));
// Colors.
t!("{#f7a20500}" Block(Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0))));
t!("{#f7a20500}" Block!(Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0))));
t!("{#a5}"
nodes: [Block(Color(RgbaColor::new(0, 0, 0, 0xff)))],
nodes: [Block!(Color(RgbaColor::new(0, 0, 0, 0xff)))],
errors: [S(1..4, "invalid color")]);
// Content.
t!("{[*Hi*]}" Block(Template![Strong, Text("Hi"), Strong]));
t!("{[*Hi*]}" Block!(Template![Strong, Text("Hi"), Strong]));
// Nested blocks.
t!("{{1}}" Block(Int(1)));
t!("{{{1+2}}}" Block(Binary(Int(1), Add, Int(2))));
t!("{{1}}" Block!(Block!(@Int(1))));
// Invalid tokens.
t!("{1u}"

View File

@ -40,6 +40,10 @@ pub enum Expr {
Dict(ExprDict),
/// A template expression: `[*Hi* there!]`.
Template(ExprTemplate),
/// A grouped expression: `(1 + 2)`.
Group(Box<Expr>),
/// A block expression: `{1 + 2}`.
Block(Box<Expr>),
}
impl Pretty for Expr {
@ -54,24 +58,31 @@ impl Pretty for Expr {
Self::Angle(v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Percent(v) => write!(p, "{}%", v).unwrap(),
Self::Color(v) => write!(p, "{}", v).unwrap(),
Self::Str(s) => write!(p, "{:?}", &s).unwrap(),
Self::Call(call) => call.pretty(p),
Self::Unary(unary) => unary.pretty(p),
Self::Binary(binary) => binary.pretty(p),
Self::Array(array) => array.pretty(p),
Self::Dict(dict) => dict.pretty(p),
Self::Template(template) => pretty_template_expr(template, p),
Self::Str(v) => write!(p, "{:?}", &v).unwrap(),
Self::Call(v) => v.pretty(p),
Self::Unary(v) => v.pretty(p),
Self::Binary(v) => v.pretty(p),
Self::Array(v) => v.pretty(p),
Self::Dict(v) => v.pretty(p),
Self::Template(v) => {
p.push_str("[");
v.pretty(p);
p.push_str("]");
}
Self::Group(v) => {
p.push_str("(");
v.pretty(p);
p.push_str(")");
}
Self::Block(v) => {
p.push_str("{");
v.pretty(p);
p.push_str("}");
}
}
}
}
/// Pretty print a template in an expression context.
pub fn pretty_template_expr(tree: &Tree, p: &mut Printer) {
p.push_str("[");
tree.pretty(p);
p.push_str("]");
}
/// An invocation of a function: `[foo ...]`, `foo(...)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprCall {
@ -197,6 +208,8 @@ impl Pretty for ExprUnary {
/// A unary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum UnOp {
/// The plus operator: `+`.
Pos,
/// The negation operator: `-`.
Neg,
}
@ -204,6 +217,7 @@ pub enum UnOp {
impl Pretty for UnOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(match self {
Self::Pos => "+",
Self::Neg => "-",
});
}
@ -302,7 +316,11 @@ mod tests {
fn test_pretty_print_expressions() {
// Unary and binary operations.
test_pretty("{1 +}", "{1}");
test_pretty("{1++1}", "{1 + +1}");
test_pretty("{+-1}", "{+-1}");
test_pretty("{1 + func(-2)}", "{1 + func(-2)}");
test_pretty("{1+2*3}", "{1 + 2 * 3}");
test_pretty("{(1+2)*3}", "{(1 + 2) * 3}");
// Array.
test_pretty("(-5,)", "(-5,)");
@ -314,6 +332,10 @@ mod tests {
// Content expression.
test_pretty("[v [[f]], 1]", "[v [[f]], 1]");
// Parens and blocks.
test_pretty("{(1)}", "{(1)}");
test_pretty("{{1}}", "{{1}}");
}
#[test]

View File

@ -34,29 +34,14 @@ impl Pretty for Node {
Self::Text(text) => p.push_str(&text),
Self::Heading(heading) => heading.pretty(p),
Self::Raw(raw) => raw.pretty(p),
Self::Expr(expr) => pretty_expr_node(expr, p),
}
}
}
/// Pretty print an expression in a node context.
pub fn pretty_expr_node(expr: &Expr, p: &mut Printer) {
match expr {
// Prefer bracket calls over expression blocks with just a single paren
// call.
//
// Example: Transforms "{v()}" => "[v]".
Expr::Call(call) => pretty_bracket_call(call, p, false),
// Remove unncessary nesting of content and expression blocks.
//
// Example: Transforms "{[Hi]}" => "Hi".
Expr::Template(template) => template.pretty(p),
_ => {
p.push_str("{");
expr.pretty(p);
p.push_str("}");
Self::Expr(expr) => {
if let Expr::Call(call) = expr {
// Format bracket calls appropriately.
pretty_bracket_call(call, p, false)
} else {
expr.pretty(p);
}
}
}
}
}
@ -178,18 +163,13 @@ mod tests {
use super::super::tests::test_pretty;
#[test]
fn test_pretty_print_removes_nesting() {
// Nesting does not matter.
test_pretty("{{x}}", "{x}");
test_pretty("{{{x}}}", "{x}");
}
#[test]
fn test_pretty_print_prefers_bracket_calls() {
// All reduces to a simple bracket call.
test_pretty("{v()}", "[v]");
fn test_pretty_print_bracket_calls() {
// Top-level call expression formatted as bracket call.
test_pretty("[v]", "[v]");
test_pretty("{[[v]]}", "[v]");
// Blocks are preserved.
test_pretty("{v()}", "{v()}");
test_pretty("{[[v]]}", "{[[v]]}");
}
#[test]