Multi-expression blocks 🛍️

This commit is contained in:
Laurenz 2021-01-26 21:13:52 +01:00
parent aaa48403cd
commit e847082435
12 changed files with 193 additions and 132 deletions

View File

@ -68,7 +68,7 @@ impl Eval for &[Spanned<Node>] {
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
for node in self { for node in self {
node.as_ref().eval(ctx); node.eval(ctx);
} }
} }
} }
@ -175,8 +175,8 @@ impl Eval for Spanned<&Expr> {
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)),
Expr::Template(v) => Value::Template(v.clone()), Expr::Template(v) => Value::Template(v.clone()),
Expr::Group(v) => v.as_ref().eval(ctx), Expr::Group(v) => v.eval(ctx),
Expr::Block(v) => v.as_ref().eval(ctx), Expr::Block(v) => v.with_span(self.span).eval(ctx),
Expr::Call(v) => v.with_span(self.span).eval(ctx), Expr::Call(v) => v.with_span(self.span).eval(ctx),
Expr::Unary(v) => v.with_span(self.span).eval(ctx), Expr::Unary(v) => v.with_span(self.span).eval(ctx),
Expr::Binary(v) => v.with_span(self.span).eval(ctx), Expr::Binary(v) => v.with_span(self.span).eval(ctx),
@ -190,7 +190,7 @@ impl Eval for Spanned<&ExprArray> {
type Output = ValueArray; type Output = ValueArray;
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
self.v.iter().map(|expr| expr.as_ref().eval(ctx)).collect() self.v.iter().map(|expr| expr.eval(ctx)).collect()
} }
} }
@ -200,16 +200,28 @@ impl Eval for Spanned<&ExprDict> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
self.v self.v
.iter() .iter()
.map(|Named { name, expr }| (name.v.0.clone(), expr.as_ref().eval(ctx))) .map(|Named { name, expr }| (name.v.0.clone(), expr.eval(ctx)))
.collect() .collect()
} }
} }
impl Eval for Spanned<&ExprBlock> {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let mut output = Value::None;
for expr in &self.v.exprs {
output = expr.eval(ctx);
}
output
}
}
impl Eval for Spanned<&ExprUnary> { impl Eval for Spanned<&ExprUnary> {
type Output = Value; type Output = Value;
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.eval(ctx);
if value == Value::Error { if value == Value::Error {
return Value::Error; return Value::Error;
} }
@ -327,8 +339,8 @@ impl Eval for Spanned<&ExprLet> {
type Output = Value; type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let value = match &self.v.expr { let value = match &self.v.init {
Some(expr) => expr.as_ref().eval(ctx), Some(expr) => expr.eval(ctx),
None => Value::None, None => Value::None,
}; };
ctx.scopes.define(self.v.pat.v.as_str(), value); ctx.scopes.define(self.v.pat.v.as_str(), value);

View File

@ -109,10 +109,13 @@ pub fn div(lhs: Value, rhs: Value) -> Value {
(Float(a), Float(b)) => Float(a / b), (Float(a), Float(b)) => Float(a / b),
(Length(a), Int(b)) => Length(a / b as f64), (Length(a), Int(b)) => Length(a / b as f64),
(Length(a), Float(b)) => Length(a / b), (Length(a), Float(b)) => Length(a / b),
(Length(a), Length(b)) => Float(a / b),
(Angle(a), Int(b)) => Angle(a / b as f64), (Angle(a), Int(b)) => Angle(a / b as f64),
(Angle(a), Float(b)) => Angle(a / b), (Angle(a), Float(b)) => Angle(a / b),
(Angle(a), Angle(b)) => Float(a / b),
(Relative(a), Int(b)) => Relative(a / b as f64), (Relative(a), Int(b)) => Relative(a / b as f64),
(Relative(a), Float(b)) => Relative(a / b), (Relative(a), Float(b)) => Relative(a / b),
(Relative(a), Relative(b)) => Float(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, _ => Error,

View File

@ -94,6 +94,14 @@ impl Div<f64> for Relative {
} }
} }
impl Div for Relative {
type Output = f64;
fn div(self, other: Self) -> f64 {
self.0 / other.0
}
}
assign_impl!(Relative += Relative); assign_impl!(Relative += Relative);
assign_impl!(Relative -= Relative); assign_impl!(Relative -= Relative);
assign_impl!(Relative *= f64); assign_impl!(Relative *= f64);

View File

@ -43,7 +43,8 @@ fn tree(p: &mut Parser) -> Tree {
/// Parse a syntax node. /// Parse a syntax node.
fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
let node = match p.peek()? { let token = p.peek()?;
let node = match token {
// Bracket call. // Bracket call.
Token::LeftBracket => { Token::LeftBracket => {
return Some(Node::Expr(bracket_call(p)?)); return Some(Node::Expr(bracket_call(p)?));
@ -75,8 +76,21 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
// Keywords. // Keywords.
Token::Let => return Some(Node::Expr(stmt_let(p)?)), Token::Let | Token::If | Token::For => {
Token::If => return Some(Node::Expr(expr_if(p)?)), let stmt = token == Token::Let;
let group = if stmt { Group::Stmt } else { Group::Expr };
p.start_group(group, TokenMode::Code);
let expr = primary(p);
if stmt && expr.is_some() && !p.eof() {
p.expected_at("semicolon or line break", p.last_end());
}
p.end_group();
// Uneat spaces we might have eaten eagerly.
p.jump(p.last_end());
return expr.map(Node::Expr);
}
// Comments. // Comments.
Token::LineComment(_) | Token::BlockComment(_) => { Token::LineComment(_) | Token::BlockComment(_) => {
@ -97,7 +111,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
fn heading(p: &mut Parser) -> NodeHeading { fn heading(p: &mut Parser) -> NodeHeading {
// Count hashtags. // Count hashtags.
let mut level = p.span(|p| { let mut level = p.span(|p| {
p.eat_assert(Token::Hash); p.assert(Token::Hash);
let mut level = 0u8; let mut level = 0u8;
while p.eat_if(Token::Hash) { while p.eat_if(Token::Hash) {
@ -194,10 +208,6 @@ fn bracket_subheader(p: &mut Parser) -> Option<ExprCall> {
p.start_group(Group::Subheader, TokenMode::Code); p.start_group(Group::Subheader, TokenMode::Code);
let name = p.span_if(ident); let name = p.span_if(ident);
if name.is_none() {
p.expected("function name");
}
let args = p.span(arguments); let args = p.span(arguments);
p.end_group(); p.end_group();
@ -215,14 +225,6 @@ fn bracket_body(p: &mut Parser) -> Tree {
tree tree
} }
/// Parse an identifier.
fn ident(p: &mut Parser) -> Option<Ident> {
p.eat_map(|token| match token {
Token::Ident(id) => Some(Ident(id.into())),
_ => None,
})
}
/// Parse an expression. /// Parse an expression.
fn expr(p: &mut Parser) -> Option<Expr> { fn expr(p: &mut Parser) -> Option<Expr> {
expr_with(p, 0) expr_with(p, 0)
@ -314,6 +316,10 @@ fn primary(p: &mut Parser) -> Option<Expr> {
Some(Token::Color(color)) => Expr::Color(color), Some(Token::Color(color)) => Expr::Color(color),
Some(Token::Str(token)) => Expr::Str(string(p, token)), Some(Token::Str(token)) => Expr::Str(string(p, token)),
// Keywords.
Some(Token::Let) => return expr_let(p),
Some(Token::If) => return expr_if(p),
// No value. // No value.
_ => { _ => {
p.expected("expression"); p.expected("expression");
@ -335,12 +341,20 @@ fn template(p: &mut Parser) -> Expr {
/// Parse a block expression: `{...}`. /// Parse a block expression: `{...}`.
fn block(p: &mut Parser) -> Option<Expr> { fn block(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Brace, TokenMode::Code); p.start_group(Group::Brace, TokenMode::Code);
let expr = p.span_if(expr); let mut exprs = vec![];
while !p.eof() { while !p.eof() {
p.unexpected(); p.start_group(Group::Stmt, TokenMode::Code);
if let Some(expr) = p.span_if(expr) {
exprs.push(expr);
if !p.eof() {
p.expected_at("semicolon or line break", p.last_end());
}
} }
p.end_group(); p.end_group();
Some(Expr::Block(Box::new(expr?))) p.skip_white();
}
p.end_group();
Some(Expr::Block(ExprBlock { exprs }))
} }
/// Parse a parenthesized function call. /// Parse a parenthesized function call.
@ -363,72 +377,69 @@ fn string(p: &mut Parser, token: TokenStr) -> String {
resolve::resolve_string(token.string) resolve::resolve_string(token.string)
} }
/// Parse a let statement. /// Parse a let expression.
fn stmt_let(p: &mut Parser) -> Option<Expr> { fn expr_let(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Stmt, TokenMode::Code); p.assert(Token::Let);
p.eat_assert(Token::Let);
let pat = match p.span_if(ident) { let mut expr_let = None;
Some(pat) => pat, if let Some(pat) = p.span_if(ident) {
None => { let mut init = None;
p.expected("identifier"); if p.eat_if(Token::Eq) {
p.end_group(); init = p.span_if(expr);
return None;
}
};
let rhs = if p.eat_if(Token::Eq) { p.span_if(expr) } else { None };
if !p.eof() {
p.expected_at("semicolon or line break", p.last_end());
} }
p.end_group(); expr_let = Some(Expr::Let(ExprLet { pat, init: init.map(Box::new) }))
}
Some(Expr::Let(ExprLet { pat, expr: rhs.map(Box::new) })) expr_let
} }
/// Parse an if expresion. /// Parse an if expresion.
fn expr_if(p: &mut Parser) -> Option<Expr> { fn expr_if(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Expr, TokenMode::Code); p.assert(Token::If);
p.eat_assert(Token::If);
let condition = match p.span_if(expr) { let mut expr_if = None;
Some(condition) => Box::new(condition), if let Some(condition) = p.span_if(expr) {
None => { if let Some(if_body) = p.span_if(body) {
p.end_group(); let mut else_body = None;
return None; if p.eat_if(Token::Else) {
else_body = p.span_if(body);
} }
};
p.end_group(); expr_if = Some(Expr::If(ExprIf {
condition: Box::new(condition),
if_body: Box::new(if_body),
else_body: else_body.map(Box::new),
}));
}
}
let if_body = Box::new(control_body(p)?); expr_if
}
let start = p.last_end();
p.skip_white();
let else_body = if p.eat_if(Token::Else) {
control_body(p).map(Box::new) /// Parse an identifier.
} else { fn ident(p: &mut Parser) -> Option<Ident> {
p.jump(start); match p.peek() {
Some(Token::Ident(id)) => {
p.eat();
Some(Ident(id.into()))
}
_ => {
p.expected("identifier");
None None
}; }
}
Some(Expr::If(ExprIf { condition, if_body, else_body }))
} }
/// Parse a control flow body. /// Parse a control flow body.
fn control_body(p: &mut Parser) -> Option<Spanned<Expr>> { fn body(p: &mut Parser) -> Option<Expr> {
let start = p.last_end();
p.skip_white();
match p.peek() { match p.peek() {
Some(Token::LeftBracket) => Some(p.span(template)), Some(Token::LeftBracket) => Some(template(p)),
Some(Token::LeftBrace) => p.span_if(block), Some(Token::LeftBrace) => block(p),
_ => { _ => {
p.expected_at("body", start); p.expected_at("body", p.last_end());
p.jump(start);
None None
} }
} }

View File

@ -103,10 +103,11 @@ impl<'s> Parser<'s> {
self.groups.push(group); self.groups.push(group);
self.repeek(); self.repeek();
match group { match group {
Group::Paren => self.eat_assert(Token::LeftParen), Group::Paren => self.assert(Token::LeftParen),
Group::Bracket => self.eat_assert(Token::LeftBracket), Group::Bracket => self.assert(Token::LeftBracket),
Group::Brace => self.eat_assert(Token::LeftBrace), Group::Brace => self.assert(Token::LeftBrace),
Group::Subheader => {} Group::Subheader => {}
Group::Stmt => {} Group::Stmt => {}
Group::Expr => {} Group::Expr => {}
@ -199,8 +200,18 @@ impl<'s> Parser<'s> {
mapped mapped
} }
/// Consume the next token if it is the given one and produce an error if
/// not.
pub fn expect(&mut self, t: Token) -> bool {
let eaten = self.eat_if(t);
if !eaten {
self.expected(t.name());
}
eaten
}
/// Consume the next token, debug-asserting that it is the given one. /// Consume the next token, debug-asserting that it is the given one.
pub fn eat_assert(&mut self, t: Token) { pub fn assert(&mut self, t: Token) {
let next = self.eat(); let next = self.eat();
debug_assert_eq!(next, Some(t)); debug_assert_eq!(next, Some(t));
} }
@ -277,6 +288,7 @@ impl<'s> Parser<'s> {
scanner scanner
} }
/// Move to the next token, skipping whitespace and comments in code mode.
fn bump(&mut self) { fn bump(&mut self) {
self.last_end = self.tokens.pos(); self.last_end = self.tokens.pos();
self.next_start = self.tokens.pos(); self.next_start = self.tokens.pos();
@ -286,11 +298,7 @@ impl<'s> Parser<'s> {
TokenMode::Markup => {} TokenMode::Markup => {}
TokenMode::Code => loop { TokenMode::Code => loop {
match self.next { match self.next {
Some(Token::Space(n)) => { Some(Token::Space(n)) if n < 1 || !self.in_line_group() => {}
if n >= 1 && self.groups.last() == Some(&Group::Stmt) {
break;
}
}
Some(Token::LineComment(_)) => {} Some(Token::LineComment(_)) => {}
Some(Token::BlockComment(_)) => {} Some(Token::BlockComment(_)) => {}
_ => break, _ => break,
@ -304,6 +312,7 @@ impl<'s> Parser<'s> {
self.repeek(); self.repeek();
} }
/// Take another look at the next token to recheck whether it ends a group.
fn repeek(&mut self) { fn repeek(&mut self) {
self.peeked = self.next; self.peeked = self.next;
let token = match self.next { let token = match self.next {
@ -316,13 +325,18 @@ impl<'s> Parser<'s> {
Token::RightBracket if self.groups.contains(&Group::Bracket) => {} Token::RightBracket if self.groups.contains(&Group::Bracket) => {}
Token::RightBrace if self.groups.contains(&Group::Brace) => {} Token::RightBrace if self.groups.contains(&Group::Brace) => {}
Token::Semicolon if self.groups.contains(&Group::Stmt) => {} Token::Semicolon if self.groups.contains(&Group::Stmt) => {}
Token::Space(n) if n >= 1 && self.groups.last() == Some(&Group::Stmt) => {} Token::Space(n) if n >= 1 && self.in_line_group() => {}
Token::Pipe if self.groups.contains(&Group::Subheader) => {} Token::Pipe if self.groups.contains(&Group::Subheader) => {}
_ => return, _ => return,
} }
self.peeked = None; self.peeked = None;
} }
/// Whether the active group ends at a newline.
fn in_line_group(&self) -> bool {
matches!(self.groups.last(), Some(&Group::Stmt) | Some(&Group::Expr))
}
} }
impl Debug for Parser<'_> { impl Debug for Parser<'_> {

View File

@ -75,11 +75,7 @@ impl Pretty for Expr {
v.v.pretty(p); v.v.pretty(p);
p.push_str(")"); p.push_str(")");
} }
Self::Block(v) => { Self::Block(v) => v.pretty(p),
p.push_str("{");
v.v.pretty(p);
p.push_str("}");
}
Self::Unary(v) => v.pretty(p), Self::Unary(v) => v.pretty(p),
Self::Binary(v) => v.pretty(p), Self::Binary(v) => v.pretty(p),
Self::Call(v) => v.pretty(p), Self::Call(v) => v.pretty(p),
@ -139,10 +135,28 @@ impl Pretty for Named {
pub type ExprTemplate = Tree; pub type ExprTemplate = Tree;
/// A grouped expression: `(1 + 2)`. /// A grouped expression: `(1 + 2)`.
pub type ExprGroup = Box<Spanned<Expr>>; pub type ExprGroup = SpanBox<Expr>;
/// A block expression: `{1 + 2}`. /// A block expression: `{1 + 2}`.
pub type ExprBlock = Box<Spanned<Expr>>; #[derive(Debug, Clone, PartialEq)]
pub struct ExprBlock {
/// The list of expressions contained in the block.
pub exprs: SpanVec<Expr>,
}
impl Pretty for ExprBlock {
fn pretty(&self, p: &mut Printer) {
p.push_str("{");
if self.exprs.len() > 1 {
p.push_str(" ");
}
p.join(&self.exprs, "; ", |expr, p| expr.v.pretty(p));
if self.exprs.len() > 1 {
p.push_str(" ");
}
p.push_str("}");
}
}
/// A unary operation: `-x`. /// A unary operation: `-x`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -150,7 +164,7 @@ pub struct ExprUnary {
/// The operator: `-`. /// The operator: `-`.
pub op: Spanned<UnOp>, pub op: Spanned<UnOp>,
/// The expression to operator on: `x`. /// The expression to operator on: `x`.
pub expr: Box<Spanned<Expr>>, pub expr: SpanBox<Expr>,
} }
impl Pretty for ExprUnary { impl Pretty for ExprUnary {
@ -213,11 +227,11 @@ impl Pretty for UnOp {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ExprBinary { pub struct ExprBinary {
/// The left-hand side of the operation: `a`. /// The left-hand side of the operation: `a`.
pub lhs: Box<Spanned<Expr>>, pub lhs: SpanBox<Expr>,
/// The operator: `+`. /// The operator: `+`.
pub op: Spanned<BinOp>, pub op: Spanned<BinOp>,
/// The right-hand side of the operation: `b`. /// The right-hand side of the operation: `b`.
pub rhs: Box<Spanned<Expr>>, pub rhs: SpanBox<Expr>,
} }
impl Pretty for ExprBinary { impl Pretty for ExprBinary {
@ -376,7 +390,7 @@ pub enum Associativity {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ExprCall { pub struct ExprCall {
/// The callee of the function. /// The callee of the function.
pub callee: Box<Spanned<Expr>>, pub callee: SpanBox<Expr>,
/// The arguments to the function. /// The arguments to the function.
pub args: Spanned<ExprArgs>, pub args: Spanned<ExprArgs>,
} }
@ -466,17 +480,17 @@ impl Pretty for Argument {
pub struct ExprLet { pub struct ExprLet {
/// The pattern to assign to. /// The pattern to assign to.
pub pat: Spanned<Ident>, pub pat: Spanned<Ident>,
/// The expression to assign to the pattern. /// The expression the pattern is initialized with.
pub expr: Option<Box<Spanned<Expr>>>, pub init: Option<SpanBox<Expr>>,
} }
impl Pretty for ExprLet { impl Pretty for ExprLet {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
p.push_str("#let "); p.push_str("#let ");
p.push_str(&self.pat.v); p.push_str(&self.pat.v);
if let Some(expr) = &self.expr { if let Some(init) = &self.init {
p.push_str(" = "); p.push_str(" = ");
expr.v.pretty(p); init.v.pretty(p);
} }
} }
} }
@ -485,11 +499,11 @@ impl Pretty for ExprLet {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ExprIf { pub struct ExprIf {
/// The condition which selects the body to evaluate. /// The condition which selects the body to evaluate.
pub condition: Box<Spanned<Expr>>, pub condition: SpanBox<Expr>,
/// The expression to evaluate if the condition is true. /// The expression to evaluate if the condition is true.
pub if_body: Box<Spanned<Expr>>, pub if_body: SpanBox<Expr>,
/// The expression to evaluate if the condition is false. /// The expression to evaluate if the condition is false.
pub else_body: Option<Box<Spanned<Expr>>>, pub else_body: Option<SpanBox<Expr>>,
} }
impl Pretty for ExprIf { impl Pretty for ExprIf {
@ -520,6 +534,7 @@ mod tests {
#[test] #[test]
fn test_pretty_print_expressions() { fn test_pretty_print_expressions() {
// Unary and binary operations. // Unary and binary operations.
test_pretty("{}", "{}");
test_pretty("{1 +}", "{1}"); test_pretty("{1 +}", "{1}");
test_pretty("{1++1}", "{1 + +1}"); test_pretty("{1++1}", "{1 + +1}");
test_pretty("{+-1}", "{+-1}"); test_pretty("{+-1}", "{+-1}");

View File

@ -34,6 +34,9 @@ impl<T> Offset for SpanVec<T> {
} }
} }
/// A box of a spanned value of type `T`.
pub type SpanBox<T> = Box<Spanned<T>>;
/// A value with the span it corresponds to in the source code. /// A value with the span it corresponds to in the source code.
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 640 B

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,21 +1,17 @@
// Empty.
{}
// Basic expression. // Basic expression.
{1} {1}
// Error: 1:2-1:2 expected expression
{}
// Bad expression. // Bad expression.
// Error: 1:2-1:4 expected expression, found invalid token // Error: 1:2-1:4 expected expression, found invalid token
{1u} {1u}
// Two expressions are not allowed.
// Error: 1:4-1:5 unexpected integer
{2 3}
// Missing closing brace in nested block. // Missing closing brace in nested block.
// Error: 1:5-1:5 expected closing brace // Error: 1:5-1:5 expected closing brace
{({1) + 2} {({1) + 1}
// Missing closing bracket in template expression. // Missing closing bracket in template expression.
// Error: 1:11-1:11 expected closing bracket // Error: 1:11-1:11 expected closing bracket
{[_] + [4_} {[_] + [3_}

View File

@ -33,18 +33,18 @@
[f|f|f] [f|f|f]
// With body. // With body.
// Error: 1:6-1:7 expected function name, found integer // Error: 1:6-1:7 expected identifier, found integer
[f | 1 | box][💕] [f | 1 | box][💕]
// Error: 2:2-2:2 expected function name // Error: 2:2-2:2 expected identifier
// Error: 1:3-1:3 expected function name // Error: 1:3-1:3 expected identifier
[||f true] [||f true]
// Error: 1:6-1:6 expected function name // Error: 1:6-1:6 expected identifier
[f 1|] [f 1|]
// Error: 2:2-2:2 expected function name // Error: 2:2-2:2 expected identifier
// Error: 1:3-1:3 expected function name // Error: 1:3-1:3 expected identifier
[|][Nope] [|][Nope]
// Error: 2:5-2:5 expected closing paren // Error: 2:5-2:5 expected closing paren
@ -80,16 +80,16 @@
#let x = "string" #let x = "string"
[x] [x]
// Error: 1:2-1:3 expected function name, found invalid token // Error: 1:2-1:3 expected identifier, found invalid token
[# 1] [# 1]
// Error: 4:1-4:1 expected function name // Error: 4:1-4:1 expected identifier
// Error: 3:1-3:1 expected closing bracket // Error: 3:1-3:1 expected closing bracket
[ [
--- ---
// Ref: false // Ref: false
// Error: 2:2-2:3 expected function name, found closing paren // Error: 2:2-2:3 expected identifier, found closing paren
// Error: 3:1-3:1 expected closing bracket // Error: 3:1-3:1 expected closing bracket
[) [)

View File

@ -6,15 +6,18 @@
// Braced condition is fine. // Braced condition is fine.
#if {true} {"3"} #if {true} {"3"}
// Newline between body and else-clause. // Newlines.
#if false [] #if false [
#else [4]
] #else [
4
]
// Multiline (condition needs parens because it's terminated by the line break, // Multiline (condition needs parens because it's terminated by the line break,
// just like the right-hand side of a let-binding). // just like the right-hand side of a let-binding).
#if #if (
x x
{ ) {
"Fi" + "ve" "Fi" + "ve"
} }
@ -44,17 +47,13 @@ a#if }
// Error: 1:16-1:16 expected body // Error: 1:16-1:16 expected body
a#if x b#if (x)c a#if x b#if (x)c
// Needs if-body expression.
// Error: 1:12-1:12 expected expression
a#if true {}
// Needs else-body. // Needs else-body.
// Error: 1:20-1:20 expected body // Error: 1:20-1:20 expected body
a#if true [b] #else c a#if true [b] #else c
// Lone else. // Lone else.
// Error: 2:1-2:6 unexpected keyword `#else` // Error: 2:1-2:6 unexpected keyword `#else`
// Error: 1:8-1:8 expected function name // Error: 1:8-1:8 expected identifier
#else [] #else []
// Condition must be boolean. If it isn't, neither branch is evaluated. // Condition must be boolean. If it isn't, neither branch is evaluated.