summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-01-26 21:13:52 +0100
committerLaurenz <laurmaedje@gmail.com>2021-01-26 21:13:52 +0100
commite8470824352064fefbb5d0d30a728211d11cea53 (patch)
tree128b49fd0609566584d1c2822db25303806e3e43 /src
parentaaa48403cdd3d8499584eeca4103865d6425ac1b (diff)
Multi-expression blocks 🛍️
Diffstat (limited to 'src')
-rw-r--r--src/eval/mod.rs28
-rw-r--r--src/eval/ops.rs3
-rw-r--r--src/geom/relative.rs8
-rw-r--r--src/parse/mod.rs141
-rw-r--r--src/parse/parser.rs34
-rw-r--r--src/syntax/expr.rs51
-rw-r--r--src/syntax/span.rs3
7 files changed, 167 insertions, 101 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 8563520e..eaa0b08f 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -68,7 +68,7 @@ impl Eval for &[Spanned<Node>] {
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
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::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)),
Expr::Template(v) => Value::Template(v.clone()),
- Expr::Group(v) => v.as_ref().eval(ctx),
- Expr::Block(v) => v.as_ref().eval(ctx),
+ Expr::Group(v) => v.eval(ctx),
+ Expr::Block(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::Binary(v) => v.with_span(self.span).eval(ctx),
@@ -190,7 +190,7 @@ impl Eval for Spanned<&ExprArray> {
type Output = ValueArray;
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 {
self.v
.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()
}
}
+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> {
type Output = Value;
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 {
return Value::Error;
}
@@ -327,8 +339,8 @@ impl Eval for Spanned<&ExprLet> {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- let value = match &self.v.expr {
- Some(expr) => expr.as_ref().eval(ctx),
+ let value = match &self.v.init {
+ Some(expr) => expr.eval(ctx),
None => Value::None,
};
ctx.scopes.define(self.v.pat.v.as_str(), value);
diff --git a/src/eval/ops.rs b/src/eval/ops.rs
index 939445f0..56d6687b 100644
--- a/src/eval/ops.rs
+++ b/src/eval/ops.rs
@@ -109,10 +109,13 @@ pub fn div(lhs: Value, rhs: Value) -> Value {
(Float(a), Float(b)) => Float(a / b),
(Length(a), Int(b)) => Length(a / b as f64),
(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), Float(b)) => Angle(a / b),
+ (Angle(a), Angle(b)) => Float(a / b),
(Relative(a), Int(b)) => Relative(a / b as f64),
(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), Float(b)) => Linear(a / b),
_ => Error,
diff --git a/src/geom/relative.rs b/src/geom/relative.rs
index cea7e4e3..1d040c72 100644
--- a/src/geom/relative.rs
+++ b/src/geom/relative.rs
@@ -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 *= f64);
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index a3a38775..f731cd17 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -43,7 +43,8 @@ fn tree(p: &mut Parser) -> Tree {
/// Parse a syntax 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.
Token::LeftBracket => {
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)),
// Keywords.
- Token::Let => return Some(Node::Expr(stmt_let(p)?)),
- Token::If => return Some(Node::Expr(expr_if(p)?)),
+ Token::Let | Token::If | Token::For => {
+ 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.
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 {
// Count hashtags.
let mut level = p.span(|p| {
- p.eat_assert(Token::Hash);
+ p.assert(Token::Hash);
let mut level = 0u8;
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);
let name = p.span_if(ident);
- if name.is_none() {
- p.expected("function name");
- }
-
let args = p.span(arguments);
p.end_group();
@@ -215,14 +225,6 @@ fn bracket_body(p: &mut Parser) -> 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.
fn expr(p: &mut Parser) -> Option<Expr> {
expr_with(p, 0)
@@ -314,6 +316,10 @@ fn primary(p: &mut Parser) -> Option<Expr> {
Some(Token::Color(color)) => Expr::Color(color),
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.
_ => {
p.expected("expression");
@@ -335,12 +341,20 @@ fn template(p: &mut Parser) -> Expr {
/// Parse a block expression: `{...}`.
fn block(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Brace, TokenMode::Code);
- let expr = p.span_if(expr);
+ let mut exprs = vec![];
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.skip_white();
}
p.end_group();
- Some(Expr::Block(Box::new(expr?)))
+ Some(Expr::Block(ExprBlock { exprs }))
}
/// Parse a parenthesized function call.
@@ -363,72 +377,69 @@ fn string(p: &mut Parser, token: TokenStr) -> String {
resolve::resolve_string(token.string)
}
-/// Parse a let statement.
-fn stmt_let(p: &mut Parser) -> Option<Expr> {
- p.start_group(Group::Stmt, TokenMode::Code);
- p.eat_assert(Token::Let);
+/// Parse a let expression.
+fn expr_let(p: &mut Parser) -> Option<Expr> {
+ p.assert(Token::Let);
- let pat = match p.span_if(ident) {
- Some(pat) => pat,
- None => {
- p.expected("identifier");
- p.end_group();
- return None;
+ let mut expr_let = None;
+ if let Some(pat) = p.span_if(ident) {
+ let mut init = None;
+ if p.eat_if(Token::Eq) {
+ init = p.span_if(expr);
}
- };
-
- 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());
+ expr_let = Some(Expr::Let(ExprLet { pat, init: init.map(Box::new) }))
}
- p.end_group();
-
- Some(Expr::Let(ExprLet { pat, expr: rhs.map(Box::new) }))
+ expr_let
}
/// Parse an if expresion.
fn expr_if(p: &mut Parser) -> Option<Expr> {
- p.start_group(Group::Expr, TokenMode::Code);
- p.eat_assert(Token::If);
+ p.assert(Token::If);
+
+ let mut expr_if = None;
+ if let Some(condition) = p.span_if(expr) {
+ if let Some(if_body) = p.span_if(body) {
+ let mut else_body = None;
+ if p.eat_if(Token::Else) {
+ else_body = p.span_if(body);
+ }
- let condition = match p.span_if(expr) {
- Some(condition) => Box::new(condition),
- None => {
- p.end_group();
- return None;
+ expr_if = Some(Expr::If(ExprIf {
+ condition: Box::new(condition),
+ if_body: Box::new(if_body),
+ else_body: else_body.map(Box::new),
+ }));
}
- };
-
- p.end_group();
+ }
- 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)
- } else {
- p.jump(start);
- None
- };
- Some(Expr::If(ExprIf { condition, if_body, else_body }))
+/// Parse an identifier.
+fn ident(p: &mut Parser) -> Option<Ident> {
+ match p.peek() {
+ Some(Token::Ident(id)) => {
+ p.eat();
+ Some(Ident(id.into()))
+ }
+ _ => {
+ p.expected("identifier");
+ None
+ }
+ }
}
/// Parse a control flow body.
-fn control_body(p: &mut Parser) -> Option<Spanned<Expr>> {
- let start = p.last_end();
- p.skip_white();
-
+fn body(p: &mut Parser) -> Option<Expr> {
match p.peek() {
- Some(Token::LeftBracket) => Some(p.span(template)),
- Some(Token::LeftBrace) => p.span_if(block),
+ Some(Token::LeftBracket) => Some(template(p)),
+ Some(Token::LeftBrace) => block(p),
_ => {
- p.expected_at("body", start);
- p.jump(start);
+ p.expected_at("body", p.last_end());
None
}
}
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
index f9ced34f..906d9e62 100644
--- a/src/parse/parser.rs
+++ b/src/parse/parser.rs
@@ -103,10 +103,11 @@ impl<'s> Parser<'s> {
self.groups.push(group);
self.repeek();
+
match group {
- Group::Paren => self.eat_assert(Token::LeftParen),
- Group::Bracket => self.eat_assert(Token::LeftBracket),
- Group::Brace => self.eat_assert(Token::LeftBrace),
+ Group::Paren => self.assert(Token::LeftParen),
+ Group::Bracket => self.assert(Token::LeftBracket),
+ Group::Brace => self.assert(Token::LeftBrace),
Group::Subheader => {}
Group::Stmt => {}
Group::Expr => {}
@@ -199,8 +200,18 @@ impl<'s> Parser<'s> {
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.
- pub fn eat_assert(&mut self, t: Token) {
+ pub fn assert(&mut self, t: Token) {
let next = self.eat();
debug_assert_eq!(next, Some(t));
}
@@ -277,6 +288,7 @@ impl<'s> Parser<'s> {
scanner
}
+ /// Move to the next token, skipping whitespace and comments in code mode.
fn bump(&mut self) {
self.last_end = self.tokens.pos();
self.next_start = self.tokens.pos();
@@ -286,11 +298,7 @@ impl<'s> Parser<'s> {
TokenMode::Markup => {}
TokenMode::Code => loop {
match self.next {
- Some(Token::Space(n)) => {
- if n >= 1 && self.groups.last() == Some(&Group::Stmt) {
- break;
- }
- }
+ Some(Token::Space(n)) if n < 1 || !self.in_line_group() => {}
Some(Token::LineComment(_)) => {}
Some(Token::BlockComment(_)) => {}
_ => break,
@@ -304,6 +312,7 @@ impl<'s> Parser<'s> {
self.repeek();
}
+ /// Take another look at the next token to recheck whether it ends a group.
fn repeek(&mut self) {
self.peeked = self.next;
let token = match self.next {
@@ -316,13 +325,18 @@ impl<'s> Parser<'s> {
Token::RightBracket if self.groups.contains(&Group::Bracket) => {}
Token::RightBrace if self.groups.contains(&Group::Brace) => {}
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) => {}
_ => return,
}
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<'_> {
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index 6be9d632..afeac988 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -75,11 +75,7 @@ impl Pretty for Expr {
v.v.pretty(p);
p.push_str(")");
}
- Self::Block(v) => {
- p.push_str("{");
- v.v.pretty(p);
- p.push_str("}");
- }
+ Self::Block(v) => v.pretty(p),
Self::Unary(v) => v.pretty(p),
Self::Binary(v) => v.pretty(p),
Self::Call(v) => v.pretty(p),
@@ -139,10 +135,28 @@ impl Pretty for Named {
pub type ExprTemplate = Tree;
/// A grouped expression: `(1 + 2)`.
-pub type ExprGroup = Box<Spanned<Expr>>;
+pub type ExprGroup = SpanBox<Expr>;
/// 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`.
#[derive(Debug, Clone, PartialEq)]
@@ -150,7 +164,7 @@ pub struct ExprUnary {
/// The operator: `-`.
pub op: Spanned<UnOp>,
/// The expression to operator on: `x`.
- pub expr: Box<Spanned<Expr>>,
+ pub expr: SpanBox<Expr>,
}
impl Pretty for ExprUnary {
@@ -213,11 +227,11 @@ impl Pretty for UnOp {
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBinary {
/// The left-hand side of the operation: `a`.
- pub lhs: Box<Spanned<Expr>>,
+ pub lhs: SpanBox<Expr>,
/// The operator: `+`.
pub op: Spanned<BinOp>,
/// The right-hand side of the operation: `b`.
- pub rhs: Box<Spanned<Expr>>,
+ pub rhs: SpanBox<Expr>,
}
impl Pretty for ExprBinary {
@@ -376,7 +390,7 @@ pub enum Associativity {
#[derive(Debug, Clone, PartialEq)]
pub struct ExprCall {
/// The callee of the function.
- pub callee: Box<Spanned<Expr>>,
+ pub callee: SpanBox<Expr>,
/// The arguments to the function.
pub args: Spanned<ExprArgs>,
}
@@ -466,17 +480,17 @@ impl Pretty for Argument {
pub struct ExprLet {
/// The pattern to assign to.
pub pat: Spanned<Ident>,
- /// The expression to assign to the pattern.
- pub expr: Option<Box<Spanned<Expr>>>,
+ /// The expression the pattern is initialized with.
+ pub init: Option<SpanBox<Expr>>,
}
impl Pretty for ExprLet {
fn pretty(&self, p: &mut Printer) {
p.push_str("#let ");
p.push_str(&self.pat.v);
- if let Some(expr) = &self.expr {
+ if let Some(init) = &self.init {
p.push_str(" = ");
- expr.v.pretty(p);
+ init.v.pretty(p);
}
}
}
@@ -485,11 +499,11 @@ impl Pretty for ExprLet {
#[derive(Debug, Clone, PartialEq)]
pub struct ExprIf {
/// 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.
- pub if_body: Box<Spanned<Expr>>,
+ pub if_body: SpanBox<Expr>,
/// 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 {
@@ -520,6 +534,7 @@ mod tests {
#[test]
fn test_pretty_print_expressions() {
// Unary and binary operations.
+ test_pretty("{}", "{}");
test_pretty("{1 +}", "{1}");
test_pretty("{1++1}", "{1 + +1}");
test_pretty("{+-1}", "{+-1}");
diff --git a/src/syntax/span.rs b/src/syntax/span.rs
index 21fa9ab8..5087dffa 100644
--- a/src/syntax/span.rs
+++ b/src/syntax/span.rs
@@ -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.
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]