summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/diag.rs6
-rw-r--r--src/eval/call.rs22
-rw-r--r--src/eval/capture.rs8
-rw-r--r--src/eval/mod.rs364
-rw-r--r--src/eval/scope.rs19
-rw-r--r--src/eval/value.rs133
-rw-r--r--src/exec/context.rs (renamed from src/eval/context.rs)54
-rw-r--r--src/exec/mod.rs161
-rw-r--r--src/exec/state.rs (renamed from src/eval/state.rs)0
-rw-r--r--src/geom/relative.rs2
-rw-r--r--src/layout/mod.rs2
-rw-r--r--src/layout/spacing.rs2
-rw-r--r--src/lib.rs35
-rw-r--r--src/library/insert.rs32
-rw-r--r--src/library/layout.rs376
-rw-r--r--src/library/style.rs113
-rw-r--r--src/main.rs4
-rw-r--r--src/parse/collection.rs69
-rw-r--r--src/parse/mod.rs260
-rw-r--r--src/parse/parser.rs139
-rw-r--r--src/parse/resolve.rs13
-rw-r--r--src/prelude.rs6
-rw-r--r--src/syntax/expr.rs397
-rw-r--r--src/syntax/ident.rs35
-rw-r--r--src/syntax/mod.rs7
-rw-r--r--src/syntax/node.rs8
-rw-r--r--src/syntax/span.rs17
-rw-r--r--src/syntax/token.rs2
-rw-r--r--src/syntax/visit.rs108
29 files changed, 1387 insertions, 1007 deletions
diff --git a/src/diag.rs b/src/diag.rs
index fca905da..07fd7b50 100644
--- a/src/diag.rs
+++ b/src/diag.rs
@@ -36,12 +36,6 @@ impl Feedback {
Self { diags: vec![], decos: vec![] }
}
- /// Merge two feedbacks into one.
- pub fn join(mut a: Self, b: Self) -> Self {
- a.extend(b);
- a
- }
-
/// Add other feedback data to this feedback.
pub fn extend(&mut self, more: Self) {
self.diags.extend(more.diags);
diff --git a/src/eval/call.rs b/src/eval/call.rs
index 7b45c09a..1a80e15a 100644
--- a/src/eval/call.rs
+++ b/src/eval/call.rs
@@ -1,21 +1,21 @@
use super::*;
-impl Eval for Spanned<&ExprCall> {
+impl Eval for ExprCall {
type Output = Value;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- let callee = self.v.callee.eval(ctx);
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ let callee = self.callee.eval(ctx);
if let Value::Func(func) = callee {
let func = func.clone();
- let mut args = self.v.args.as_ref().eval(ctx);
+ let mut args = self.args.eval(ctx);
let returned = func(ctx, &mut args);
args.finish(ctx);
return returned;
} else if callee != Value::Error {
ctx.diag(error!(
- self.v.callee.span,
+ self.callee.span(),
"expected function, found {}",
callee.type_name(),
));
@@ -25,22 +25,22 @@ impl Eval for Spanned<&ExprCall> {
}
}
-impl Eval for Spanned<&ExprArgs> {
+impl Eval for ExprArgs {
type Output = Args;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
let mut pos = vec![];
let mut named = vec![];
- for arg in self.v {
+ for arg in &self.items {
match arg {
Argument::Pos(expr) => {
- pos.push(expr.as_ref().eval(ctx).with_span(expr.span));
+ pos.push(expr.eval(ctx).with_span(expr.span()));
}
Argument::Named(Named { name, expr }) => {
named.push((
- name.as_ref().map(|id| id.0.clone()),
- expr.as_ref().eval(ctx).with_span(expr.span),
+ name.string.clone().with_span(name.span),
+ expr.eval(ctx).with_span(expr.span()),
));
}
}
diff --git a/src/eval/capture.rs b/src/eval/capture.rs
index 9ef55fb2..bbabc503 100644
--- a/src/eval/capture.rs
+++ b/src/eval/capture.rs
@@ -50,11 +50,11 @@ impl<'ast> Visit<'ast> for CapturesVisitor<'_> {
fn visit_block(&mut self, item: &'ast ExprBlock) {
// Blocks create a scope except if directly in a template.
- if item.scopes {
+ if item.scoping {
self.internal.push();
}
visit_block(self, item);
- if item.scopes {
+ if item.scoping {
self.internal.pop();
}
}
@@ -67,12 +67,12 @@ impl<'ast> Visit<'ast> for CapturesVisitor<'_> {
}
fn visit_let(&mut self, item: &'ast ExprLet) {
- self.define(&item.pat.v);
+ self.define(&item.binding);
visit_let(self, item);
}
fn visit_for(&mut self, item: &'ast ExprFor) {
- match &item.pat.v {
+ match &item.pattern {
ForPattern::Value(value) => self.define(value),
ForPattern::KeyValue(key, value) => {
self.define(key);
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index e49f7779..2390a84f 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -1,247 +1,199 @@
-//! Evaluation of syntax trees into layout trees.
+//! Evaluation of syntax trees.
#[macro_use]
mod value;
mod call;
mod capture;
-mod context;
mod ops;
mod scope;
-mod state;
pub use call::*;
pub use capture::*;
-pub use context::*;
pub use scope::*;
-pub use state::*;
pub use value::*;
+use std::collections::HashMap;
use std::rc::Rc;
+use super::*;
use crate::color::Color;
-use crate::diag::Pass;
-use crate::env::Env;
-use crate::geom::{Angle, Length, Relative, Spec};
-use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
+use crate::diag::{Diag, Feedback};
+use crate::geom::{Angle, Length, Relative};
use crate::syntax::visit::Visit;
use crate::syntax::*;
-/// Evaluate a syntax tree into a layout tree.
+/// Evaluate all expressions in a syntax tree.
///
-/// The `state` is the base state that may be updated over the course of
-/// evaluation. The `scope` similarly consists of the base definitions that are
-/// present from the beginning (typically, the standard library).
-pub fn eval(
- tree: &Tree,
- env: &mut Env,
- scope: &Scope,
- state: State,
-) -> Pass<layout::Tree> {
- let mut ctx = EvalContext::new(env, scope, state);
- ctx.start_page_group(Softness::Hard);
- tree.eval(&mut ctx);
- ctx.end_page_group(|s| s == Softness::Hard);
- ctx.finish()
+/// The `scope` consists of the base definitions that are present from the
+/// beginning (typically, the standard library).
+pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass<ExprMap> {
+ let mut ctx = EvalContext::new(env, scope);
+ let map = tree.eval(&mut ctx);
+ Pass::new(map, ctx.feedback)
}
-/// Evaluate an item.
+/// A map from expression to values to evaluated to.
///
-/// _Note_: Evaluation is not necessarily pure, it may change the active state.
-pub trait Eval {
- /// The output of evaluating the item.
- type Output;
-
- /// Evaluate the item to the output value.
- fn eval(self, ctx: &mut EvalContext) -> Self::Output;
+/// The raw pointers point into the expressions contained in `tree`. Since
+/// the lifetime is erased, `tree` could go out of scope while the hash map
+/// still lives. While this could lead to lookup panics, it is not unsafe
+/// since the pointers are never dereferenced.
+pub type ExprMap = HashMap<*const Expr, Value>;
+
+/// The context for evaluation.
+#[derive(Debug)]
+pub struct EvalContext<'a> {
+ /// The environment from which resources are gathered.
+ pub env: &'a mut Env,
+ /// The active scopes.
+ pub scopes: Scopes<'a>,
+ /// The accumulated feedback.
+ feedback: Feedback,
}
-impl<'a, T> Eval for &'a 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 = ();
-
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- for node in self {
- node.eval(ctx);
+impl<'a> EvalContext<'a> {
+ /// Create a new execution context with a base scope.
+ pub fn new(env: &'a mut Env, scope: &'a Scope) -> Self {
+ Self {
+ env,
+ scopes: Scopes::with_base(scope),
+ feedback: Feedback::new(),
}
}
-}
-impl Eval for Spanned<&Node> {
- type Output = ();
-
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- match self.v {
- Node::Text(text) => {
- let node = ctx.make_text_node(text.clone());
- ctx.push(node);
- }
- Node::Space => {
- let em = ctx.state.font.font_size();
- ctx.push(NodeSpacing {
- amount: ctx.state.par.word_spacing.resolve(em),
- softness: Softness::Soft,
- });
- }
- Node::Linebreak => ctx.apply_linebreak(),
- Node::Parbreak => ctx.apply_parbreak(),
- Node::Strong => ctx.state.font.strong ^= true,
- Node::Emph => ctx.state.font.emph ^= true,
- Node::Heading(heading) => heading.with_span(self.span).eval(ctx),
- Node::Raw(raw) => raw.with_span(self.span).eval(ctx),
- Node::Expr(expr) => {
- let value = expr.with_span(self.span).eval(ctx);
- value.eval(ctx)
- }
- }
+ /// Add a diagnostic to the feedback.
+ pub fn diag(&mut self, diag: Spanned<Diag>) {
+ self.feedback.diags.push(diag);
}
}
-impl Eval for Spanned<&NodeHeading> {
- type Output = ();
-
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- let prev = ctx.state.clone();
- let upscale = 1.5 - 0.1 * self.v.level.v as f64;
- ctx.state.font.scale *= upscale;
- ctx.state.font.strong = true;
-
- self.v.contents.eval(ctx);
- ctx.apply_parbreak();
+/// Evaluate an expression.
+pub trait Eval {
+ /// The output of evaluating the expression.
+ type Output;
- ctx.state = prev;
- }
+ /// Evaluate the expression to the output value.
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output;
}
-impl Eval for Spanned<&NodeRaw> {
- type Output = ();
-
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- let prev = Rc::clone(&ctx.state.font.families);
- let families = ctx.state.font.families_mut();
- families.list.insert(0, "monospace".to_string());
- families.flatten();
-
- let em = ctx.state.font.font_size();
- let line_spacing = ctx.state.par.line_spacing.resolve(em);
-
- let mut children = vec![];
- for line in &self.v.lines {
- children.push(layout::Node::Text(ctx.make_text_node(line.clone())));
- children.push(layout::Node::Spacing(NodeSpacing {
- amount: line_spacing,
- softness: Softness::Hard,
- }));
- }
+impl Eval for Tree {
+ type Output = ExprMap;
- if self.v.block {
- ctx.apply_parbreak();
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ struct ExprVisitor<'a, 'b> {
+ map: ExprMap,
+ ctx: &'a mut EvalContext<'b>,
}
- ctx.push(NodeStack {
- dirs: ctx.state.dirs,
- align: ctx.state.align,
- expand: Spec::uniform(Expansion::Fit),
- children,
- });
-
- if self.v.block {
- ctx.apply_parbreak();
+ impl<'ast> Visit<'ast> for ExprVisitor<'_, '_> {
+ fn visit_expr(&mut self, item: &'ast Expr) {
+ self.map.insert(item as *const _, item.eval(self.ctx));
+ }
}
- ctx.state.font.families = prev;
+ let mut visitor = ExprVisitor { map: HashMap::new(), ctx };
+ visitor.visit_tree(self);
+ visitor.map
}
}
-impl Eval for Spanned<&Expr> {
+impl Eval for Expr {
type Output = Value;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- match self.v {
- Expr::None => Value::None,
- Expr::Ident(v) => match ctx.scopes.get(v) {
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ match self {
+ Self::Lit(lit) => lit.eval(ctx),
+ Self::Ident(v) => match ctx.scopes.get(&v) {
Some(slot) => slot.borrow().clone(),
None => {
- ctx.diag(error!(self.span, "unknown variable"));
+ ctx.diag(error!(v.span, "unknown variable"));
Value::Error
}
},
- &Expr::Bool(v) => Value::Bool(v),
- &Expr::Int(v) => Value::Int(v),
- &Expr::Float(v) => Value::Float(v),
- &Expr::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
- &Expr::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
- &Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
- &Expr::Color(v) => Value::Color(Color::Rgba(v)),
- Expr::Str(v) => Value::Str(v.clone()),
- Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)),
- Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)),
- Expr::Template(v) => v.with_span(self.span).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),
- Expr::Let(v) => v.with_span(self.span).eval(ctx),
- Expr::If(v) => v.with_span(self.span).eval(ctx),
- Expr::For(v) => v.with_span(self.span).eval(ctx),
+ Self::Array(v) => Value::Array(v.eval(ctx)),
+ Self::Dict(v) => Value::Dict(v.eval(ctx)),
+ Self::Template(v) => Value::Template(vec![v.eval(ctx)]),
+ Self::Group(v) => v.eval(ctx),
+ Self::Block(v) => v.eval(ctx),
+ Self::Call(v) => v.eval(ctx),
+ Self::Unary(v) => v.eval(ctx),
+ Self::Binary(v) => v.eval(ctx),
+ Self::Let(v) => v.eval(ctx),
+ Self::If(v) => v.eval(ctx),
+ Self::For(v) => v.eval(ctx),
+ }
+ }
+}
+
+impl Eval for Lit {
+ type Output = Value;
+
+ fn eval(&self, _: &mut EvalContext) -> Self::Output {
+ match self.kind {
+ LitKind::None => Value::None,
+ LitKind::Bool(v) => Value::Bool(v),
+ LitKind::Int(v) => Value::Int(v),
+ LitKind::Float(v) => Value::Float(v),
+ LitKind::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
+ LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
+ LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
+ LitKind::Color(v) => Value::Color(Color::Rgba(v)),
+ LitKind::Str(ref v) => Value::Str(v.clone()),
}
}
}
-impl Eval for Spanned<&ExprArray> {
+impl Eval for ExprArray {
type Output = ValueArray;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- self.v.iter().map(|expr| expr.eval(ctx)).collect()
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ self.items.iter().map(|expr| expr.eval(ctx)).collect()
}
}
-impl Eval for Spanned<&ExprDict> {
+impl Eval for ExprDict {
type Output = ValueDict;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- self.v
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ self.items
.iter()
- .map(|Named { name, expr }| (name.v.0.clone(), expr.eval(ctx)))
+ .map(|Named { name, expr }| (name.string.clone(), expr.eval(ctx)))
.collect()
}
}
-impl Eval for Spanned<&ExprTemplate> {
+impl Eval for ExprTemplate {
+ type Output = TemplateNode;
+
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ let tree = Rc::clone(&self.tree);
+ let map = self.tree.eval(ctx);
+ TemplateNode::Tree { tree, map }
+ }
+}
+
+impl Eval for ExprGroup {
type Output = Value;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- let mut template = self.v.clone();
- let mut visitor = CapturesVisitor::new(&ctx.scopes);
- visitor.visit_template(&mut template);
- Value::Template(template)
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ self.expr.eval(ctx)
}
}
-impl Eval for Spanned<&ExprBlock> {
+impl Eval for ExprBlock {
type Output = Value;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- if self.v.scopes {
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ if self.scoping {
ctx.scopes.push();
}
let mut output = Value::None;
- for expr in &self.v.exprs {
+ for expr in &self.exprs {
output = expr.eval(ctx);
}
- if self.v.scopes {
+ if self.scoping {
ctx.scopes.pop();
}
@@ -249,17 +201,17 @@ impl Eval for Spanned<&ExprBlock> {
}
}
-impl Eval for Spanned<&ExprUnary> {
+impl Eval for ExprUnary {
type Output = Value;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- let value = self.v.expr.eval(ctx);
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ let value = self.expr.eval(ctx);
if value == Value::Error {
return Value::Error;
}
let ty = value.type_name();
- let out = match self.v.op.v {
+ let out = match self.op {
UnOp::Pos => ops::pos(value),
UnOp::Neg => ops::neg(value),
UnOp::Not => ops::not(value),
@@ -269,7 +221,7 @@ impl Eval for Spanned<&ExprUnary> {
ctx.diag(error!(
self.span,
"cannot apply '{}' to {}",
- self.v.op.v.as_str(),
+ self.op.as_str(),
ty,
));
}
@@ -278,11 +230,11 @@ impl Eval for Spanned<&ExprUnary> {
}
}
-impl Eval for Spanned<&ExprBinary> {
+impl Eval for ExprBinary {
type Output = Value;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- match self.v.op.v {
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ match self.op {
BinOp::Add => self.apply(ctx, ops::add),
BinOp::Sub => self.apply(ctx, ops::sub),
BinOp::Mul => self.apply(ctx, ops::mul),
@@ -304,22 +256,22 @@ impl Eval for Spanned<&ExprBinary> {
}
}
-impl Spanned<&ExprBinary> {
+impl ExprBinary {
/// Apply a basic binary operation.
- fn apply<F>(self, ctx: &mut EvalContext, op: F) -> Value
+ fn apply<F>(&self, ctx: &mut EvalContext, op: F) -> Value
where
F: FnOnce(Value, Value) -> Value,
{
- let lhs = self.v.lhs.eval(ctx);
+ let lhs = self.lhs.eval(ctx);
// Short-circuit boolean operations.
- match (self.v.op.v, &lhs) {
+ match (self.op, &lhs) {
(BinOp::And, Value::Bool(false)) => return lhs,
(BinOp::Or, Value::Bool(true)) => return lhs,
_ => {}
}
- let rhs = self.v.rhs.eval(ctx);
+ let rhs = self.rhs.eval(ctx);
if lhs == Value::Error || rhs == Value::Error {
return Value::Error;
@@ -336,23 +288,23 @@ impl Spanned<&ExprBinary> {
}
/// Apply an assignment operation.
- fn assign<F>(self, ctx: &mut EvalContext, op: F) -> Value
+ 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;
+ let rhs = self.rhs.eval(ctx);
- let slot = if let Expr::Ident(id) = &self.v.lhs.v {
+ let lhs_span = self.lhs.span();
+ let slot = if let Expr::Ident(id) = self.lhs.as_ref() {
match ctx.scopes.get(id) {
Some(slot) => slot,
None => {
- ctx.diag(error!(span, "unknown variable"));
+ ctx.diag(error!(lhs_span, "unknown variable"));
return Value::Error;
}
}
} else {
- ctx.diag(error!(span, "cannot assign to this expression"));
+ ctx.diag(error!(lhs_span, "cannot assign to this expression"));
return Value::Error;
};
@@ -371,7 +323,7 @@ impl Spanned<&ExprBinary> {
};
if constant {
- ctx.diag(error!(span, "cannot assign to a constant"));
+ ctx.diag(error!(lhs_span, "cannot assign to a constant"));
}
if let Some((l, r)) = err {
@@ -382,47 +334,45 @@ impl Spanned<&ExprBinary> {
}
fn error(&self, ctx: &mut EvalContext, l: &str, r: &str) {
- let op = self.v.op.v.as_str();
- let message = match self.v.op.v {
+ ctx.diag(error!(self.span, "{}", match self.op {
BinOp::Add => format!("cannot add {} and {}", l, r),
BinOp::Sub => format!("cannot subtract {1} from {0}", l, r),
BinOp::Mul => format!("cannot multiply {} with {}", l, r),
BinOp::Div => format!("cannot divide {} by {}", l, r),
- _ => format!("cannot apply '{}' to {} and {}", op, l, r),
- };
- ctx.diag(error!(self.span, "{}", message));
+ _ => format!("cannot apply '{}' to {} and {}", self.op.as_str(), l, r),
+ }));
}
}
-impl Eval for Spanned<&ExprLet> {
+impl Eval for ExprLet {
type Output = Value;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- let value = match &self.v.init {
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ let value = match &self.init {
Some(expr) => expr.eval(ctx),
None => Value::None,
};
- ctx.scopes.def_mut(self.v.pat.v.as_str(), value);
+ ctx.scopes.def_mut(self.binding.as_str(), value);
Value::None
}
}
-impl Eval for Spanned<&ExprIf> {
+impl Eval for ExprIf {
type Output = Value;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- let condition = self.v.condition.eval(ctx);
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
+ let condition = self.condition.eval(ctx);
if let Value::Bool(boolean) = condition {
return if boolean {
- self.v.if_body.eval(ctx)
- } else if let Some(expr) = &self.v.else_body {
+ self.if_body.eval(ctx)
+ } else if let Some(expr) = &self.else_body {
expr.eval(ctx)
} else {
Value::None
};
} else if condition != Value::Error {
ctx.diag(error!(
- self.v.condition.span,
+ self.condition.span(),
"expected boolean, found {}",
condition.type_name(),
));
@@ -432,10 +382,10 @@ impl Eval for Spanned<&ExprIf> {
}
}
-impl Eval for Spanned<&ExprFor> {
+impl Eval for ExprFor {
type Output = Value;
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
+ fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
macro_rules! iterate {
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {{
let mut output = vec![];
@@ -444,7 +394,7 @@ impl Eval for Spanned<&ExprFor> {
for ($($value),*) in $iter {
$(ctx.scopes.def_mut($binding.as_str(), $value);)*
- if let Value::Template(new) = self.v.body.eval(ctx) {
+ if let Value::Template(new) = self.body.eval(ctx) {
output.extend(new);
}
}
@@ -455,8 +405,8 @@ impl Eval for Spanned<&ExprFor> {
ctx.scopes.push();
- let iter = self.v.iter.eval(ctx);
- let value = match (self.v.pat.v.clone(), iter) {
+ let iter = self.iter.eval(ctx);
+ let value = match (self.pattern.clone(), iter) {
(ForPattern::Value(v), Value::Str(string)) => {
iterate!(for (v => value) in string.chars().map(|c| Value::Str(c.into())))
}
@@ -472,14 +422,14 @@ impl Eval for Spanned<&ExprFor> {
(ForPattern::KeyValue(_, _), Value::Str(_))
| (ForPattern::KeyValue(_, _), Value::Array(_)) => {
- ctx.diag(error!(self.v.pat.span, "mismatched pattern"));
+ ctx.diag(error!(self.pattern.span(), "mismatched pattern"));
Value::Error
}
(_, Value::Error) => Value::Error,
(_, iter) => {
ctx.diag(error!(
- self.v.iter.span,
+ self.iter.span(),
"cannot loop over {}",
iter.type_name(),
));
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index 8f2bd1d5..0991564f 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -21,9 +21,22 @@ pub struct Scopes<'a> {
}
impl<'a> Scopes<'a> {
- /// Create a new hierarchy of scopes.
- pub fn new(base: Option<&'a Scope>) -> Self {
- Self { top: Scope::new(), scopes: vec![], base }
+ /// Create a new, empty hierarchy of scopes.
+ pub fn new() -> Self {
+ Self {
+ top: Scope::new(),
+ scopes: vec![],
+ base: None,
+ }
+ }
+
+ /// Create a new hierarchy of scopes with a base scope.
+ pub fn with_base(base: &'a Scope) -> Self {
+ Self {
+ top: Scope::new(),
+ scopes: vec![],
+ base: Some(base),
+ }
}
/// Push a new scope.
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 119a2f1b..dd1221e5 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -4,11 +4,12 @@ use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Deref;
use std::rc::Rc;
-use super::{Args, Eval, EvalContext};
+use super::*;
use crate::color::Color;
+use crate::exec::ExecContext;
use crate::geom::{Angle, Length, Linear, Relative};
-use crate::pretty::{pretty, Pretty, Printer};
-use crate::syntax::{pretty_template, Spanned, Tree, WithSpan};
+use crate::pretty::{Pretty, Printer};
+use crate::syntax::Tree;
/// A computational value.
#[derive(Debug, Clone, PartialEq)]
@@ -48,12 +49,12 @@ pub enum Value {
}
impl Value {
- /// Try to cast the value into a specific type.
- pub fn cast<T>(self) -> CastResult<T, Self>
+ /// Create a new template value consisting of a single dynamic node.
+ pub fn template<F>(f: F) -> Self
where
- T: Cast<Value>,
+ F: Fn(&mut ExecContext) + 'static,
{
- T::cast(self)
+ Self::Template(vec![TemplateNode::Any(TemplateAny::new(f))])
}
/// The name of the stored value's type.
@@ -77,26 +78,13 @@ impl Value {
Self::Error => "error",
}
}
-}
-
-impl Eval for &Value {
- type Output = ();
- /// Evaluate everything contained in this value.
- fn eval(self, ctx: &mut EvalContext) -> Self::Output {
- ctx.push(ctx.make_text_node(match self {
- Value::None => return,
- Value::Str(s) => s.clone(),
- Value::Template(tree) => {
- // We do not want to allow the template access to the current
- // scopes.
- let prev = std::mem::take(&mut ctx.scopes);
- tree.eval(ctx);
- ctx.scopes = prev;
- return;
- }
- other => pretty(other),
- }));
+ /// Try to cast the value into a specific type.
+ pub fn cast<T>(self) -> CastResult<T, Self>
+ where
+ T: Cast<Value>,
+ {
+ T::cast(self)
}
}
@@ -121,7 +109,7 @@ impl Pretty for Value {
Value::Str(v) => v.pretty(p),
Value::Array(v) => v.pretty(p),
Value::Dict(v) => v.pretty(p),
- Value::Template(v) => pretty_template(v, p),
+ Value::Template(v) => v.pretty(p),
Value::Func(v) => v.pretty(p),
Value::Any(v) => v.pretty(p),
Value::Error => p.push_str("(error)"),
@@ -163,7 +151,88 @@ impl Pretty for ValueDict {
}
/// A template value: `[*Hi* there]`.
-pub type ValueTemplate = Tree;
+pub type ValueTemplate = Vec<TemplateNode>;
+
+impl Pretty for ValueTemplate {
+ fn pretty(&self, p: &mut Printer) {
+ p.push('[');
+ for part in self {
+ part.pretty(p);
+ }
+ p.push(']');
+ }
+}
+
+/// One chunk of a template.
+///
+/// Evaluating a template expression creates only a single chunk. Adding two
+/// such templates yields a two-chunk template.
+#[derive(Debug, Clone, PartialEq)]
+pub enum TemplateNode {
+ /// A template that consists of a syntax tree plus already evaluated
+ /// expression.
+ Tree {
+ /// The tree of this template part.
+ tree: Rc<Tree>,
+ /// The evaluated expressions.
+ map: ExprMap,
+ },
+ /// A template that can implement custom behaviour.
+ Any(TemplateAny),
+}
+
+impl Pretty for TemplateNode {
+ fn pretty(&self, p: &mut Printer) {
+ match self {
+ // TODO: Pretty-print the values.
+ Self::Tree { tree, .. } => tree.pretty(p),
+ Self::Any(any) => any.pretty(p),
+ }
+ }
+}
+
+/// A reference-counted dynamic template node (can implement custom behaviour).
+#[derive(Clone)]
+pub struct TemplateAny {
+ f: Rc<dyn Fn(&mut ExecContext)>,
+}
+
+impl TemplateAny {
+ /// Create a new dynamic template value from a rust function or closure.
+ pub fn new<F>(f: F) -> Self
+ where
+ F: Fn(&mut ExecContext) + 'static,
+ {
+ Self { f: Rc::new(f) }
+ }
+}
+
+impl PartialEq for TemplateAny {
+ fn eq(&self, _: &Self) -> bool {
+ // TODO: Figure out what we want here.
+ false
+ }
+}
+
+impl Deref for TemplateAny {
+ type Target = dyn Fn(&mut ExecContext);
+
+ fn deref(&self) -> &Self::Target {
+ self.f.as_ref()
+ }
+}
+
+impl Pretty for TemplateAny {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str("<any>");
+ }
+}
+
+impl Debug for TemplateAny {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_struct("TemplateAny").finish()
+ }
+}
/// A wrapper around a reference-counted executable function.
#[derive(Clone)]
@@ -184,6 +253,7 @@ impl ValueFunc {
impl PartialEq for ValueFunc {
fn eq(&self, _: &Self) -> bool {
+ // TODO: Figure out what we want here.
false
}
}
@@ -497,9 +567,7 @@ macro_rules! impl_type {
mod tests {
use super::*;
use crate::color::RgbaColor;
- use crate::parse::parse;
use crate::pretty::pretty;
- use crate::syntax::Node;
#[track_caller]
fn test_pretty(value: impl Into<Value>, exp: &str) {
@@ -517,7 +585,6 @@ mod tests {
test_pretty(Relative::new(0.3) + Length::cm(2.0), "30.0% + 2.0cm");
test_pretty(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
test_pretty("hello", r#""hello""#);
- test_pretty(vec![Spanned::zero(Node::Strong)], "[*]");
test_pretty(ValueFunc::new("nil", |_, _| Value::None), "nil");
test_pretty(ValueAny::new(1), "1");
test_pretty(Value::Error, "(error)");
@@ -533,8 +600,8 @@ mod tests {
// Dictionary.
let mut dict = BTreeMap::new();
dict.insert("one".into(), Value::Int(1));
- dict.insert("two".into(), Value::Template(parse("#[f]").output));
+ dict.insert("two".into(), Value::Bool(false));
test_pretty(BTreeMap::new(), "(:)");
- test_pretty(dict, "(one: 1, two: #[f])");
+ test_pretty(dict, "(one: 1, two: false)");
}
}
diff --git a/src/eval/context.rs b/src/exec/context.rs
index fd7e264f..a1998f29 100644
--- a/src/eval/context.rs
+++ b/src/exec/context.rs
@@ -11,14 +11,12 @@ use crate::layout::{
Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree,
};
-/// The context for evaluation.
+/// The context for execution.
#[derive(Debug)]
-pub struct EvalContext<'a> {
+pub struct ExecContext<'a> {
/// The environment from which resources are gathered.
pub env: &'a mut Env,
- /// The active scopes.
- pub scopes: Scopes<'a>,
- /// The active evaluation state.
+ /// The active execution state.
pub state: State,
/// The accumulated feedback.
feedback: Feedback,
@@ -35,12 +33,11 @@ pub struct EvalContext<'a> {
inner: Vec<Node>,
}
-impl<'a> EvalContext<'a> {
- /// Create a new evaluation context with a base state and scope.
- pub fn new(env: &'a mut Env, scope: &'a Scope, state: State) -> Self {
+impl<'a> ExecContext<'a> {
+ /// Create a new execution context with a base state.
+ pub fn new(env: &'a mut Env, state: State) -> Self {
Self {
env,
- scopes: Scopes::new(Some(scope)),
state,
groups: vec![],
inner: vec![],
@@ -49,7 +46,7 @@ impl<'a> EvalContext<'a> {
}
}
- /// Finish evaluation and return the created document.
+ /// Finish execution and return the created layout tree.
pub fn finish(self) -> Pass<Tree> {
assert!(self.groups.is_empty(), "unfinished group");
Pass::new(Tree { runs: self.runs }, self.feedback)
@@ -226,21 +223,19 @@ impl<'a> EvalContext<'a> {
}
}
- /// Apply a forced line break.
- pub fn apply_linebreak(&mut self) {
- self.end_par_group();
- self.start_par_group();
- }
-
- /// Apply a forced paragraph break.
- pub fn apply_parbreak(&mut self) {
- self.end_par_group();
+ /// Push a normal space.
+ pub fn push_space(&mut self) {
let em = self.state.font.font_size();
self.push(NodeSpacing {
- amount: self.state.par.par_spacing.resolve(em),
+ amount: self.state.par.word_spacing.resolve(em),
softness: Softness::Soft,
});
- self.start_par_group();
+ }
+
+ /// Push a text node.
+ pub fn push_text(&mut self, text: impl Into<String>) {
+ let node = self.make_text_node(text.into());
+ self.push(node);
}
/// Construct a text node from the given string based on the active text
@@ -269,6 +264,23 @@ impl<'a> EvalContext<'a> {
variant,
}
}
+
+ /// Apply a forced line break.
+ pub fn apply_linebreak(&mut self) {
+ self.end_par_group();
+ self.start_par_group();
+ }
+
+ /// Apply a forced paragraph break.
+ pub fn apply_parbreak(&mut self) {
+ self.end_par_group();
+ let em = self.state.font.font_size();
+ self.push(NodeSpacing {
+ amount: self.state.par.par_spacing.resolve(em),
+ softness: Softness::Soft,
+ });
+ self.start_par_group();
+ }
}
/// Defines how an item interacts with surrounding items.
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
new file mode 100644
index 00000000..25edcce3
--- /dev/null
+++ b/src/exec/mod.rs
@@ -0,0 +1,161 @@
+//! Execution of syntax trees.
+
+mod context;
+mod state;
+
+pub use context::*;
+pub use state::*;
+
+use std::rc::Rc;
+
+use crate::diag::Pass;
+use crate::env::Env;
+use crate::eval::{ExprMap, TemplateAny, TemplateNode, Value, ValueTemplate};
+use crate::geom::Spec;
+use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
+use crate::pretty::pretty;
+use crate::syntax::*;
+
+/// Execute a syntax tree to produce a layout tree.
+///
+/// The `map` shall be an expression map computed for this tree with
+/// [`eval`](crate::eval::eval). Note that `tree` must be the _exact_ same tree
+/// as used for evaluation (no cloned version), because the expression map
+/// depends on the pointers being stable.
+///
+/// The `state` is the base state that may be updated over the course of
+/// execution.
+pub fn exec(
+ env: &mut Env,
+ tree: &Tree,
+ map: &ExprMap,
+ state: State,
+) -> Pass<layout::Tree> {
+ let mut ctx = ExecContext::new(env, state);
+ ctx.start_page_group(Softness::Hard);
+ tree.exec_with(&mut ctx, &map);
+ ctx.end_page_group(|s| s == Softness::Hard);
+ ctx.finish()
+}
+
+/// Execute a node.
+///
+/// This manipulates active styling and document state and produces layout
+/// nodes. Because syntax nodes and layout nodes do not correspond one-to-one,
+/// constructed layout nodes are pushed into the context instead of returned.
+/// The context takes care of reshaping the nodes into the correct tree
+/// structure.
+pub trait Exec {
+ /// Execute the node.
+ fn exec(&self, ctx: &mut ExecContext);
+}
+
+/// Execute a node with an expression map that applies to it.
+pub trait ExecWith {
+ /// Execute the node.
+ fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap);
+}
+
+impl ExecWith for Tree {
+ fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap) {
+ for node in self {
+ match node {
+ Node::Text(text) => ctx.push_text(text),
+ Node::Space => ctx.push_space(),
+ Node::Linebreak => ctx.apply_linebreak(),
+ Node::Parbreak => ctx.apply_parbreak(),
+ Node::Strong => ctx.state.font.strong ^= true,
+ Node::Emph => ctx.state.font.emph ^= true,
+ Node::Heading(heading) => heading.exec_with(ctx, map),
+ Node::Raw(raw) => raw.exec(ctx),
+ Node::Expr(expr) => map[&(expr as *const _)].exec(ctx),
+ }
+ }
+ }
+}
+
+impl ExecWith for NodeHeading {
+ fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap) {
+ let prev = ctx.state.clone();
+ let upscale = 1.5 - 0.1 * self.level as f64;
+ ctx.state.font.scale *= upscale;
+ ctx.state.font.strong = true;
+
+ self.contents.exec_with(ctx, map);
+ ctx.apply_parbreak();
+
+ ctx.state = prev;
+ }
+}
+
+impl Exec for NodeRaw {
+ fn exec(&self, ctx: &mut ExecContext) {
+ let prev = Rc::clone(&ctx.state.font.families);
+ let families = ctx.state.font.families_mut();
+ families.list.insert(0, "monospace".to_string());
+ families.flatten();
+
+ let em = ctx.state.font.font_size();
+ let line_spacing = ctx.state.par.line_spacing.resolve(em);
+
+ let mut children = vec![];
+ for line in &self.lines {
+ children.push(layout::Node::Text(ctx.make_text_node(line.clone())));
+ children.push(layout::Node::Spacing(NodeSpacing {
+ amount: line_spacing,
+ softness: Softness::Hard,
+ }));
+ }
+
+ if self.block {
+ ctx.apply_parbreak();
+ }
+
+ ctx.push(NodeStack {
+ dirs: ctx.state.dirs,
+ align: ctx.state.align,
+ expand: Spec::uniform(Expansion::Fit),
+ children,
+ });
+
+ if self.block {
+ ctx.apply_parbreak();
+ }
+
+ ctx.state.font.families = prev;
+ }
+}
+
+impl Exec for Value {
+ fn exec(&self, ctx: &mut ExecContext) {
+ match self {
+ Value::None => {}
+ Value::Str(s) => ctx.push_text(s),
+ Value::Template(template) => template.exec(ctx),
+ other => ctx.push_text(pretty(other)),
+ }
+ }
+}
+
+impl Exec for ValueTemplate {
+ fn exec(&self, ctx: &mut ExecContext) {
+ for part in self {
+ part.exec(ctx);
+ }
+ }
+}
+
+impl Exec for TemplateNode {
+ fn exec(&self, ctx: &mut ExecContext) {
+ match self {
+ Self::Tree { tree, map } => tree.exec_with(ctx, &map),
+ Self::Any(any) => any.exec(ctx),
+ }
+ }
+}
+
+impl Exec for TemplateAny {
+ fn exec(&self, ctx: &mut ExecContext) {
+ self(ctx);
+ }
+}
diff --git a/src/eval/state.rs b/src/exec/state.rs
index 21fb7fb6..21fb7fb6 100644
--- a/src/eval/state.rs
+++ b/src/exec/state.rs
diff --git a/src/geom/relative.rs b/src/geom/relative.rs
index 1d040c72..0eca911e 100644
--- a/src/geom/relative.rs
+++ b/src/geom/relative.rs
@@ -3,7 +3,7 @@ use super::*;
/// A relative length.
///
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
-/// corresponding [literal](crate::syntax::Expr::Percent).
+/// corresponding [literal](crate::syntax::LitKind::Percent).
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
pub struct Relative(f64);
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 30295841..1974b578 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -24,7 +24,7 @@ pub use stack::*;
pub use text::*;
/// Layout a tree into a collection of frames.
-pub fn layout(tree: &Tree, env: &mut Env) -> Vec<Frame> {
+pub fn layout(env: &mut Env, tree: &Tree) -> Vec<Frame> {
tree.layout(&mut LayoutContext { env })
}
diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs
index 1d6c7f9c..4b564a2b 100644
--- a/src/layout/spacing.rs
+++ b/src/layout/spacing.rs
@@ -1,7 +1,7 @@
use std::fmt::{self, Debug, Formatter};
use super::*;
-use crate::eval::Softness;
+use crate::exec::Softness;
/// A spacing node.
#[derive(Copy, Clone, PartialEq)]
diff --git a/src/lib.rs b/src/lib.rs
index 39ca47a3..efdbb4cb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,10 +5,13 @@
//! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax
//! tree]. The structures describing the tree can be found in the [syntax]
//! module.
-//! - **Evaluation:** The next step is to [evaluate] the parsed "script" into a
-//! [layout tree], a high-level, fully styled representation. The nodes of
-//! this tree are fully self-contained and order-independent and thus much
-//! better suited for layouting than the syntax tree.
+//! - **Evaluation:** The next step is to [evaluate] the syntax tree. This
+//! computes the value of each expression in document and stores them in a map
+//! from expression-pointers to values.
+//! - **Execution:** Now, we can [execute] the parsed and evaluated "script".
+//! This produces a [layout tree], a high-level, fully styled representation.
+//! The nodes of this tree are self-contained and order-independent and thus
+//! much better suited for layouting than the syntax tree.
//! - **Layouting:** Next, the tree is to [layouted] into a portable version of
//! the typeset document. The output of this is a vector of [`Frame`]s
//! (corresponding to pages), ready for exporting.
@@ -20,6 +23,7 @@
//! [parsed]: parse::parse
//! [syntax tree]: syntax::Tree
//! [evaluate]: eval::eval
+//! [execute]: exec::exec
//! [layout tree]: layout::Tree
//! [layouted]: layout::layout
//! [_PDF_]: export::pdf
@@ -30,6 +34,7 @@ pub mod diag;
pub mod eval;
pub mod color;
pub mod env;
+pub mod exec;
pub mod export;
pub mod font;
pub mod geom;
@@ -42,21 +47,27 @@ pub mod pretty;
pub mod shaping;
pub mod syntax;
-use crate::diag::{Feedback, Pass};
+use crate::diag::Pass;
use crate::env::Env;
-use crate::eval::{Scope, State};
+use crate::eval::Scope;
+use crate::exec::State;
use crate::layout::Frame;
/// Process _Typst_ source code directly into a collection of frames.
pub fn typeset(
- src: &str,
env: &mut Env,
+ src: &str,
scope: &Scope,
state: State,
) -> Pass<Vec<Frame>> {
- let Pass { output: syntax_tree, feedback: f1 } = parse::parse(src);
- let Pass { output: layout_tree, feedback: f2 } =
- eval::eval(&syntax_tree, env, scope, state);
- let frames = layout::layout(&layout_tree, env);
- Pass::new(frames, Feedback::join(f1, f2))
+ let parsed = parse::parse(src);
+ let evaluated = eval::eval(env, &parsed.output, scope);
+ let executed = exec::exec(env, &parsed.output, &evaluated.output, state);
+ let frames = layout::layout(env, &executed.output);
+
+ let mut feedback = parsed.feedback;
+ feedback.extend(evaluated.feedback);
+ feedback.extend(executed.feedback);
+
+ Pass::new(frames, feedback)
}
diff --git a/src/library/insert.rs b/src/library/insert.rs
index 58e8a11c..eff54e91 100644
--- a/src/library/insert.rs
+++ b/src/library/insert.rs
@@ -15,23 +15,23 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value {
let width = args.get(ctx, "width");
let height = args.get(ctx, "height");
- if let Some(path) = path {
- let loaded = ctx.env.resources.load(path.v, ImageResource::parse);
- if let Some((res, img)) = loaded {
- let dimensions = img.buf.dimensions();
- ctx.push(NodeImage {
- res,
- dimensions,
- width,
- height,
- align: ctx.state.align,
- });
- } else {
- ctx.diag(error!(path.span, "failed to load image"));
+ Value::template(move |ctx| {
+ if let Some(path) = &path {
+ let loaded = ctx.env.resources.load(&path.v, ImageResource::parse);
+ if let Some((res, img)) = loaded {
+ let dimensions = img.buf.dimensions();
+ ctx.push(NodeImage {
+ res,
+ dimensions,
+ width,
+ height,
+ align: ctx.state.align,
+ });
+ } else {
+ ctx.diag(error!(path.span, "failed to load image"));
+ }
}
- }
-
- Value::None
+ })
}
/// An image node.
diff --git a/src/library/layout.rs b/src/library/layout.rs
index e4839953..44c98536 100644
--- a/src/library/layout.rs
+++ b/src/library/layout.rs
@@ -1,9 +1,9 @@
use std::fmt::{self, Display, Formatter};
-use crate::layout::{Expansion, Fill, NodeFixed, NodeSpacing, NodeStack};
+use crate::exec::Softness;
+use crate::layout::{Expansion, Fill, NodeBackground, NodeFixed, NodeSpacing, NodeStack};
use crate::paper::{Paper, PaperClass};
use crate::prelude::*;
-use crate::{eval::Softness, layout::NodeBackground};
/// `align`: Align content along the layouting axes.
///
@@ -27,88 +27,91 @@ use crate::{eval::Softness, layout::NodeBackground};
/// - `bottom`
/// - `center`
pub fn align(ctx: &mut EvalContext, args: &mut Args) -> Value {
- let snapshot = ctx.state.clone();
-
let first = args.find(ctx);
let second = args.find(ctx);
let hor = args.get(ctx, "horizontal");
let ver = args.get(ctx, "vertical");
-
- let mut had = Gen::uniform(false);
- let mut had_center = false;
-
- for (axis, Spanned { v: arg, span }) in first
- .into_iter()
- .chain(second.into_iter())
- .map(|arg: Spanned<Alignment>| (arg.v.axis(), arg))
- .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
- .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)))
- {
- // Check whether we know which axis this alignment belongs to.
- if let Some(axis) = axis {
- // We know the axis.
- let gen_axis = axis.switch(ctx.state.dirs);
- let gen_align = arg.switch(ctx.state.dirs);
-
- if arg.axis().map_or(false, |a| a != axis) {
- ctx.diag(error!(span, "invalid alignment for {} axis", axis));
- } else if had.get(gen_axis) {
- ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
- } else {
- *ctx.state.align.get_mut(gen_axis) = gen_align;
- *had.get_mut(gen_axis) = true;
- }
- } else {
- // We don't know the axis: This has to be a `center` alignment for a
- // positional argument.
- debug_assert_eq!(arg, Alignment::Center);
-
- if had.main && had.cross {
- ctx.diag(error!(span, "duplicate alignment"));
- } else if had_center {
- // Both this and the previous one are unspecified `center`
- // alignments. Both axes should be centered.
- ctx.state.align.main = Align::Center;
- ctx.state.align.cross = Align::Center;
- had = Gen::uniform(true);
+ let body = args.find::<ValueTemplate>(ctx);
+
+ Value::template(move |ctx| {
+ let snapshot = ctx.state.clone();
+
+ let mut had = Gen::uniform(false);
+ let mut had_center = false;
+
+ // Infer the axes alignments belong to.
+ for (axis, Spanned { v: arg, span }) in first
+ .into_iter()
+ .chain(second.into_iter())
+ .map(|arg: Spanned<Alignment>| (arg.v.axis(), arg))
+ .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
+ .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)))
+ {
+ // Check whether we know which axis this alignment belongs to.
+ if let Some(axis) = axis {
+ // We know the axis.
+ let gen_axis = axis.switch(ctx.state.dirs);
+ let gen_align = arg.switch(ctx.state.dirs);
+
+ if arg.axis().map_or(false, |a| a != axis) {
+ ctx.diag(error!(span, "invalid alignment for {} axis", axis));
+ } else if had.get(gen_axis) {
+ ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
+ } else {
+ *ctx.state.align.get_mut(gen_axis) = gen_align;
+ *had.get_mut(gen_axis) = true;
+ }
} else {
- had_center = true;
+ // We don't know the axis: This has to be a `center` alignment for a
+ // positional argument.
+ debug_assert_eq!(arg, Alignment::Center);
+
+ if had.main && had.cross {
+ ctx.diag(error!(span, "duplicate alignment"));
+ } else if had_center {
+ // Both this and the previous one are unspecified `center`
+ // alignments. Both axes should be centered.
+ ctx.state.align.main = Align::Center;
+ ctx.state.align.cross = Align::Center;
+ had = Gen::uniform(true);
+ } else {
+ had_center = true;
+ }
}
- }
- // If we we know the other alignment, we can handle the unspecified
- // `center` alignment.
- if had_center && (had.main || had.cross) {
- if had.main {
- ctx.state.align.cross = Align::Center;
- had.cross = true;
- } else {
- ctx.state.align.main = Align::Center;
- had.main = true;
+ // If we we know the other alignment, we can handle the unspecified
+ // `center` alignment.
+ if had_center && (had.main || had.cross) {
+ if had.main {
+ ctx.state.align.cross = Align::Center;
+ had.cross = true;
+ } else {
+ ctx.state.align.main = Align::Center;
+ had.main = true;
+ }
+ had_center = false;
}
- had_center = false;
}
- }
-
- // If `had_center` wasn't flushed by now, it's the only argument and then we
- // default to applying it to the cross axis.
- if had_center {
- ctx.state.align.cross = Align::Center;
- }
- if ctx.state.align.main != snapshot.align.main {
- ctx.end_par_group();
- ctx.start_par_group();
- }
+ // If `had_center` wasn't flushed by now, it's the only argument and then we
+ // default to applying it to the cross axis.
+ if had_center {
+ ctx.state.align.cross = Align::Center;
+ }
- if let Some(body) = args.find::<ValueTemplate>(ctx) {
- body.eval(ctx);
- ctx.state = snapshot;
- }
+ if ctx.state.align.main != snapshot.align.main {
+ ctx.end_par_group();
+ ctx.start_par_group();
+ }
- Value::None
+ if let Some(body) = &body {
+ body.exec(ctx);
+ ctx.state = snapshot;
+ }
+ })
}
+/// An alignment argument.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum Alignment {
Left,
@@ -173,10 +176,10 @@ impl Display for Alignment {
/// `box`: Layout content into a box.
///
/// # Named arguments
-/// - Width of the box: `width`, of type `linear` relative to parent width.
-/// - Height of the box: `height`, of type `linear` relative to parent height.
-/// - Main layouting direction: `main-dir`, of type `direction`.
-/// - Cross layouting direction: `cross-dir`, of type `direction`.
+/// - Width of the box: `width`, of type `linear` relative to parent width.
+/// - Height of the box: `height`, of type `linear` relative to parent height.
+/// - Main layouting direction: `main-dir`, of type `direction`.
+/// - Cross layouting direction: `cross-dir`, of type `direction`.
/// - Background color of the box: `color`, of type `color`.
///
/// # Relevant types and constants
@@ -186,47 +189,45 @@ impl Display for Alignment {
/// - `ttb` (top to bottom)
/// - `btt` (bottom to top)
pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value {
- let snapshot = ctx.state.clone();
-
let width = args.get(ctx, "width");
let height = args.get(ctx, "height");
let main = args.get(ctx, "main-dir");
let cross = args.get(ctx, "cross-dir");
let color = args.get(ctx, "color");
+ let body = args.find::<ValueTemplate>(ctx);
- ctx.set_dirs(Gen::new(main, cross));
-
- let dirs = ctx.state.dirs;
- let align = ctx.state.align;
-
- ctx.start_content_group();
-
- if let Some(body) = args.find::<ValueTemplate>(ctx) {
- body.eval(ctx);
- }
+ Value::template(move |ctx| {
+ let snapshot = ctx.state.clone();
- let children = ctx.end_content_group();
+ ctx.set_dirs(Gen::new(main, cross));
+ let dirs = ctx.state.dirs;
+ let align = ctx.state.align;
- let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
- let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
-
- let fixed_node = NodeFixed {
- width,
- height,
- child: NodeStack { dirs, align, expand, children }.into(),
- };
+ ctx.start_content_group();
+ if let Some(body) = &body {
+ body.exec(ctx);
+ }
+ let children = ctx.end_content_group();
+
+ let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
+ let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
+ let fixed = NodeFixed {
+ width,
+ height,
+ child: NodeStack { dirs, align, expand, children }.into(),
+ };
- if let Some(color) = color {
- ctx.push(NodeBackground {
- fill: Fill::Color(color),
- child: fixed_node.into(),
- })
- } else {
- ctx.push(fixed_node);
- }
+ if let Some(color) = color {
+ ctx.push(NodeBackground {
+ fill: Fill::Color(color),
+ child: fixed.into(),
+ });
+ } else {
+ ctx.push(fixed);
+ }
- ctx.state = snapshot;
- Value::None
+ ctx.state = snapshot;
+ })
}
impl_type! {
@@ -253,19 +254,19 @@ pub fn v(ctx: &mut EvalContext, args: &mut Args) -> Value {
fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value {
let spacing: Option<Linear> = args.require(ctx, "spacing");
- if let Some(linear) = spacing {
- let amount = linear.resolve(ctx.state.font.font_size());
- let spacing = NodeSpacing { amount, softness: Softness::Hard };
- if axis == ctx.state.dirs.main.axis() {
- ctx.end_par_group();
- ctx.push(spacing);
- ctx.start_par_group();
- } else {
- ctx.push(spacing);
+ Value::template(move |ctx| {
+ if let Some(linear) = spacing {
+ let amount = linear.resolve(ctx.state.font.font_size());
+ let spacing = NodeSpacing { amount, softness: Softness::Hard };
+ if axis == ctx.state.dirs.main.axis() {
+ ctx.end_par_group();
+ ctx.push(spacing);
+ ctx.start_par_group();
+ } else {
+ ctx.push(spacing);
+ }
}
- }
-
- Value::None
+ })
}
/// `page`: Configure pages.
@@ -275,88 +276,103 @@ fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value {
/// full list of all paper names.
///
/// # Named arguments
-/// - Width of the page: `width`, of type `length`.
-/// - Height of the page: `height`, of type `length`.
-/// - Margins for all sides: `margins`, of type `linear` relative to sides.
-/// - Left margin: `left`, of type `linear` relative to width.
-/// - Right margin: `right`, of type `linear` relative to width.
-/// - Top margin: `top`, of type `linear` relative to height.
-/// - Bottom margin: `bottom`, of type `linear` relative to height.
-/// - Flip width and height: `flip`, of type `bool`.
+/// - Width of the page: `width`, of type `length`.
+/// - Height of the page: `height`, of type `length`.
+/// - Margins for all sides: `margins`, of type `linear` relative to sides.
+/// - Left margin: `left`, of type `linear` relative to width.
+/// - Right margin: `right`, of type `linear` relative to width.
+/// - Top margin: `top`, of type `linear` relative to height.
+/// - Bottom margin: `bottom`, of type `linear` relative to height.
+/// - Flip width and height: `flip`, of type `bool`.
+/// - Main layouting direction: `main-dir`, of type `direction`.
+/// - Cross layouting direction: `cross-dir`, of type `direction`.
pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
- let snapshot = ctx.state.clone();
+ let paper = args.find::<Spanned<String>>(ctx).and_then(|name| {
+ Paper::from_name(&name.v).or_else(|| {
+ ctx.diag(error!(name.span, "invalid paper name"));
+ None
+ })
+ });
+ let width = args.get(ctx, "width");
+ let height = args.get(ctx, "height");
+ let margins = args.get(ctx, "margins");
+ let left = args.get(ctx, "left");
+ let top = args.get(ctx, "top");
+ let right = args.get(ctx, "right");
+ let bottom = args.get(ctx, "bottom");
+ let flip = args.get(ctx, "flip");
+ let main = args.get(ctx, "main-dir");
+ let cross = args.get(ctx, "cross-dir");
+ let body = args.find::<ValueTemplate>(ctx);
+
+ Value::template(move |ctx| {
+ let snapshot = ctx.state.clone();
- if let Some(name) = args.find::<Spanned<String>>(ctx) {
- if let Some(paper) = Paper::from_name(&name.v) {
+ if let Some(paper) = paper {
ctx.state.page.class = paper.class;
ctx.state.page.size = paper.size();
ctx.state.page.expand = Spec::uniform(Expansion::Fill);
- } else {
- ctx.diag(error!(name.span, "invalid paper name"));
}
- }
-
- if let Some(width) = args.get(ctx, "width") {
- ctx.state.page.class = PaperClass::Custom;
- ctx.state.page.size.width = width;
- ctx.state.page.expand.horizontal = Expansion::Fill;
- }
- if let Some(height) = args.get(ctx, "height") {
- ctx.state.page.class = PaperClass::Custom;
- ctx.state.page.size.height = height;
- ctx.state.page.expand.vertical = Expansion::Fill;
- }
+ if let Some(width) = width {
+ ctx.state.page.class = PaperClass::Custom;
+ ctx.state.page.size.width = width;
+ ctx.state.page.expand.horizontal = Expansion::Fill;
+ }
- if let Some(margins) = args.get(ctx, "margins") {
- ctx.state.page.margins = Sides::uniform(Some(margins));
- }
+ if let Some(height) = height {
+ ctx.state.page.class = PaperClass::Custom;
+ ctx.state.page.size.height = height;
+ ctx.state.page.expand.vertical = Expansion::Fill;
+ }
- if let Some(left) = args.get(ctx, "left") {
- ctx.state.page.margins.left = Some(left);
- }
+ if let Some(margins) = margins {
+ ctx.state.page.margins = Sides::uniform(Some(margins));
+ }
- if let Some(top) = args.get(ctx, "top") {
- ctx.state.page.margins.top = Some(top);
- }
+ if let Some(left) = left {
+ ctx.state.page.margins.left = Some(left);
+ }
- if let Some(right) = args.get(ctx, "right") {
- ctx.state.page.margins.right = Some(right);
- }
+ if let Some(top) = top {
+ ctx.state.page.margins.top = Some(top);
+ }
- if let Some(bottom) = args.get(ctx, "bottom") {
- ctx.state.page.margins.bottom = Some(bottom);
- }
+ if let Some(right) = right {
+ ctx.state.page.margins.right = Some(right);
+ }
- if args.get(ctx, "flip").unwrap_or(false) {
- let page = &mut ctx.state.page;
- std::mem::swap(&mut page.size.width, &mut page.size.height);
- std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical);
- }
+ if let Some(bottom) = bottom {
+ ctx.state.page.margins.bottom = Some(bottom);
+ }
- let main = args.get(ctx, "main-dir");
- let cross = args.get(ctx, "cross-dir");
+ if flip.unwrap_or(false) {
+ let page = &mut ctx.state.page;
+ std::mem::swap(&mut page.size.width, &mut page.size.height);
+ std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical);
+ }
- ctx.set_dirs(Gen::new(main, cross));
- let mut softness = ctx.end_page_group(|_| false);
- if let Some(body) = args.find::<ValueTemplate>(ctx) {
- // TODO: Restrict body to a single page?
- ctx.start_page_group(Softness::Hard);
- body.eval(ctx);
- ctx.end_page_group(|s| s == Softness::Hard);
- softness = Softness::Soft;
- ctx.state = snapshot;
- }
+ ctx.set_dirs(Gen::new(main, cross));
- ctx.start_page_group(softness);
+ let mut softness = ctx.end_page_group(|_| false);
+ if let Some(body) = &body {
+ // TODO: Restrict body to a single page?
+ ctx.start_page_group(Softness::Hard);
+ body.exec(ctx);
+ ctx.end_page_group(|s| s == Softness::Hard);
+ softness = Softness::Soft;
+ ctx.state = snapshot;
+ }
- Value::None
+ ctx.start_page_group(softness);
+ })
}
/// `pagebreak`: Start a new page.
-pub fn pagebreak(ctx: &mut EvalContext, _: &mut Args) -> Value {
- ctx.end_page_group(|_| true);
- ctx.start_page_group(Softness::Hard);
- Value::None
+pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> Value {
+ Value::template(move |ctx| {
+ ctx.end_page_group(|_| true);
+ ctx.start_page_group(Softness::Hard);
+ })
}
diff --git a/src/library/style.rs b/src/library/style.rs
index 670104d6..23bd5298 100644
--- a/src/library/style.rs
+++ b/src/library/style.rs
@@ -55,68 +55,71 @@ use crate::prelude::*;
/// - `extra-expanded`
/// - `ultra-expanded`
pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value {
- let snapshot = ctx.state.clone();
-
- if let Some(linear) = args.find::<Linear>(ctx) {
- if linear.rel.is_zero() {
- ctx.state.font.size = linear.abs;
- ctx.state.font.scale = Relative::ONE.into();
- } else {
- ctx.state.font.scale = linear;
- }
- }
-
+ let size = args.find::<Linear>(ctx);
let list: Vec<_> = args.filter::<FontFamily>(ctx).map(|f| f.to_string()).collect();
- if !list.is_empty() {
- let families = ctx.state.font.families_mut();
- families.list = list;
- families.flatten();
- }
+ let style = args.get(ctx, "style");
+ let weight = args.get(ctx, "weight");
+ let stretch = args.get(ctx, "stretch");
+ let serif = args.get(ctx, "serif");
+ let sans_serif = args.get(ctx, "sans-serif");
+ let monospace = args.get(ctx, "monospace");
+ let body = args.find::<ValueTemplate>(ctx);
+
+ Value::template(move |ctx| {
+ let snapshot = ctx.state.clone();
+
+ if let Some(linear) = size {
+ if linear.rel.is_zero() {
+ ctx.state.font.size = linear.abs;
+ ctx.state.font.scale = Relative::ONE.into();
+ } else {
+ ctx.state.font.scale = linear;
+ }
+ }
- if let Some(style) = args.get(ctx, "style") {
- ctx.state.font.variant.style = style;
- }
+ if !list.is_empty() {
+ let families = ctx.state.font.families_mut();
+ families.list = list.clone();
+ families.flatten();
+ }
- if let Some(weight) = args.get(ctx, "weight") {
- ctx.state.font.variant.weight = weight;
- }
+ if let Some(style) = style {
+ ctx.state.font.variant.style = style;
+ }
- if let Some(stretch) = args.get(ctx, "stretch") {
- ctx.state.font.variant.stretch = stretch;
- }
+ if let Some(weight) = weight {
+ ctx.state.font.variant.weight = weight;
+ }
- for variant in FontFamily::VARIANTS {
- if let Some(FontFamilies(list)) = args.get(ctx, variant.as_str()) {
- let strings = list.into_iter().map(|f| f.to_string()).collect();
- let families = ctx.state.font.families_mut();
- families.update_class_list(variant.to_string(), strings);
- families.flatten();
+ if let Some(stretch) = stretch {
+ ctx.state.font.variant.stretch = stretch;
}
- }
- if let Some(body) = args.find::<ValueTemplate>(ctx) {
- body.eval(ctx);
- ctx.state = snapshot;
- }
+ for (variant, arg) in &[
+ (FontFamily::Serif, &serif),
+ (FontFamily::SansSerif, &sans_serif),
+ (FontFamily::Monospace, &monospace),
+ ] {
+ if let Some(FontFamilies(list)) = arg {
+ let strings = list.into_iter().map(|f| f.to_string()).collect();
+ let families = ctx.state.font.families_mut();
+ families.update_class_list(variant.to_string(), strings);
+ families.flatten();
+ }
+ }
- Value::None
+ if let Some(body) = &body {
+ body.exec(ctx);
+ ctx.state = snapshot;
+ }
+ })
}
/// A list of font families.
#[derive(Debug, Clone, PartialEq)]
struct FontFamilies(Vec<FontFamily>);
-impl_type! {
- FontFamilies: "font family or array of font families",
- Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]),
- Value::Array(values) => Self(values
- .into_iter()
- .filter_map(|v| v.cast().ok())
- .collect()
- ),
- #(family: FontFamily) => Self(vec![family]),
-}
-
+/// A single font family.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum FontFamily {
Serif,
@@ -126,9 +129,6 @@ pub(crate) enum FontFamily {
}
impl FontFamily {
- pub const VARIANTS: &'static [Self] =
- &[Self::Serif, Self::SansSerif, Self::Monospace];
-
pub fn as_str(&self) -> &str {
match self {
Self::Serif => "serif",
@@ -146,6 +146,17 @@ impl Display for FontFamily {
}
impl_type! {
+ FontFamilies: "font family or array of font families",
+ Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]),
+ Value::Array(values) => Self(values
+ .into_iter()
+ .filter_map(|v| v.cast().ok())
+ .collect()
+ ),
+ #(family: FontFamily) => Self(vec![family]),
+}
+
+impl_type! {
FontFamily: "font family",
Value::Str(string) => Self::Named(string.to_lowercase())
}
diff --git a/src/main.rs b/src/main.rs
index 07eb673a..d9a4f096 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,7 +6,7 @@ use fontdock::fs::FsIndex;
use typst::diag::{Feedback, Pass};
use typst::env::{Env, ResourceLoader};
-use typst::eval::State;
+use typst::exec::State;
use typst::export::pdf;
use typst::font::FsIndexExt;
use typst::library;
@@ -51,7 +51,7 @@ fn main() -> anyhow::Result<()> {
let Pass {
output: frames,
feedback: Feedback { mut diags, .. },
- } = typeset(&src, &mut env, &scope, state);
+ } = typeset(&mut env, &src, &scope, state);
if !diags.is_empty() {
diags.sort();
diff --git a/src/parse/collection.rs b/src/parse/collection.rs
index 95ca9847..162a8bd5 100644
--- a/src/parse/collection.rs
+++ b/src/parse/collection.rs
@@ -1,8 +1,10 @@
use super::*;
/// Parse the arguments to a function call.
-pub fn arguments(p: &mut Parser) -> ExprArgs {
- collection(p, vec![])
+pub fn args(p: &mut Parser) -> ExprArgs {
+ let start = p.start();
+ let items = collection(p, vec![]);
+ ExprArgs { span: p.span_from(start), items }
}
/// Parse a parenthesized group, which can be either of:
@@ -16,8 +18,8 @@ pub fn parenthesized(p: &mut Parser) -> Expr {
} else {
collection(p, State::Unknown)
};
- p.end_group();
- state.into_expr()
+ let span = p.end_group();
+ state.into_expr(span)
}
/// Parse a collection.
@@ -25,7 +27,7 @@ fn collection<T: Collection>(p: &mut Parser, mut collection: T) -> T {
let mut missing_coma = None;
while !p.eof() {
- if let Some(arg) = p.span_if(argument) {
+ if let Some(arg) = argument(p) {
collection.push_arg(p, arg);
if let Some(pos) = missing_coma.take() {
@@ -36,7 +38,7 @@ fn collection<T: Collection>(p: &mut Parser, mut collection: T) -> T {
break;
}
- let behind = p.last_end();
+ let behind = p.end();
if p.eat_if(Token::Comma) {
collection.push_comma();
} else {
@@ -50,14 +52,12 @@ fn collection<T: Collection>(p: &mut Parser, mut collection: T) -> T {
/// Parse an expression or a named pair.
fn argument(p: &mut Parser) -> Option<Argument> {
- let first = p.span_if(expr)?;
+ let first = expr(p)?;
if p.eat_if(Token::Colon) {
- if let Expr::Ident(ident) = first.v {
- let name = ident.with_span(first.span);
- let expr = p.span_if(expr)?;
- Some(Argument::Named(Named { name, expr }))
+ if let Expr::Ident(name) = first {
+ Some(Argument::Named(Named { name, expr: expr(p)? }))
} else {
- p.diag(error!(first.span, "expected identifier"));
+ p.diag(error!(first.span(), "expected identifier"));
expr(p);
None
}
@@ -68,13 +68,13 @@ fn argument(p: &mut Parser) -> Option<Argument> {
/// Abstraction for comma-separated list of expression / named pairs.
trait Collection {
- fn push_arg(&mut self, p: &mut Parser, arg: Spanned<Argument>);
+ fn push_arg(&mut self, p: &mut Parser, arg: Argument);
fn push_comma(&mut self) {}
}
-impl Collection for ExprArgs {
- fn push_arg(&mut self, _: &mut Parser, arg: Spanned<Argument>) {
- self.push(arg.v);
+impl Collection for Vec<Argument> {
+ fn push_arg(&mut self, _: &mut Parser, arg: Argument) {
+ self.push(arg);
}
}
@@ -82,38 +82,38 @@ impl Collection for ExprArgs {
#[derive(Debug)]
enum State {
Unknown,
- Expr(Spanned<Expr>),
- Array(ExprArray),
- Dict(ExprDict),
+ Expr(Expr),
+ Array(Vec<Expr>),
+ Dict(Vec<Named>),
}
impl State {
- fn into_expr(self) -> Expr {
+ fn into_expr(self, span: Span) -> Expr {
match self {
- Self::Unknown => Expr::Array(vec![]),
- Self::Expr(expr) => Expr::Group(Box::new(expr)),
- Self::Array(array) => Expr::Array(array),
- Self::Dict(dict) => Expr::Dict(dict),
+ Self::Unknown => Expr::Array(ExprArray { span, items: vec![] }),
+ Self::Expr(expr) => Expr::Group(ExprGroup { span, expr: Box::new(expr) }),
+ Self::Array(items) => Expr::Array(ExprArray { span, items }),
+ Self::Dict(items) => Expr::Dict(ExprDict { span, items }),
}
}
}
impl Collection for State {
- fn push_arg(&mut self, p: &mut Parser, arg: Spanned<Argument>) {
+ fn push_arg(&mut self, p: &mut Parser, arg: Argument) {
match self {
- Self::Unknown => match arg.v {
+ Self::Unknown => match arg {
Argument::Pos(expr) => *self = Self::Expr(expr),
Argument::Named(named) => *self = Self::Dict(vec![named]),
},
- Self::Expr(prev) => match arg.v {
+ Self::Expr(prev) => match arg {
Argument::Pos(expr) => *self = Self::Array(vec![take(prev), expr]),
Argument::Named(_) => diag(p, arg),
},
- Self::Array(array) => match arg.v {
+ Self::Array(array) => match arg {
Argument::Pos(expr) => array.push(expr),
Argument::Named(_) => diag(p, arg),
},
- Self::Dict(dict) => match arg.v {
+ Self::Dict(dict) => match arg {
Argument::Pos(_) => diag(p, arg),
Argument::Named(named) => dict.push(named),
},
@@ -127,13 +127,16 @@ impl Collection for State {
}
}
-fn take(expr: &mut Spanned<Expr>) -> Spanned<Expr> {
+fn take(expr: &mut Expr) -> Expr {
// Replace with anything, it's overwritten anyway.
- std::mem::replace(expr, Spanned::zero(Expr::Bool(false)))
+ std::mem::replace(
+ expr,
+ Expr::Lit(Lit { span: Span::ZERO, kind: LitKind::None }),
+ )
}
-fn diag(p: &mut Parser, arg: Spanned<Argument>) {
- p.diag(error!(arg.span, "{}", match arg.v {
+fn diag(p: &mut Parser, arg: Argument) {
+ p.diag(error!(arg.span(), "{}", match arg {
Argument::Pos(_) => "expected named pair, found expression",
Argument::Named(_) => "expected expression, found named pair",
}));
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 3fd2cca5..2c34d7b8 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -13,10 +13,11 @@ pub use resolve::*;
pub use scanner::*;
pub use tokens::*;
+use std::rc::Rc;
+
use crate::diag::Pass;
use crate::syntax::*;
-
-use collection::{arguments, parenthesized};
+use collection::{args, parenthesized};
/// Parse a string of source code.
pub fn parse(src: &str) -> Pass<Tree> {
@@ -31,8 +32,8 @@ fn tree(p: &mut Parser) -> Tree {
let mut at_start = true;
let mut tree = vec![];
while !p.eof() {
- if let Some(node) = p.span_if(|p| node(p, &mut at_start)) {
- if !matches!(node.v, Node::Parbreak | Node::Space) {
+ if let Some(node) = node(p, &mut at_start) {
+ if !matches!(node, Node::Parbreak | Node::Space) {
at_start = false;
}
tree.push(node);
@@ -78,12 +79,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
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.expected_at("semicolon or line break", p.end());
}
p.end_group();
// Uneat spaces we might have eaten eagerly.
- p.jump(p.last_end());
+ p.jump(p.end());
return expr.map(Node::Expr);
}
@@ -123,28 +124,24 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
/// Parse a heading.
fn heading(p: &mut Parser) -> Node {
- // Count depth.
- let mut level = p.span(|p| {
- p.assert(&[Token::Eq]);
+ let start = p.start();
+ p.assert(&[Token::Eq]);
- let mut level = 0u8;
- while p.eat_if(Token::Eq) {
- level = level.saturating_add(1);
- }
- level
- });
+ // Count depth.
+ let mut level: usize = 0;
+ while p.eat_if(Token::Eq) {
+ level += 1;
+ }
- if level.v > 5 {
- p.diag(warning!(level.span, "should not exceed depth 6"));
- level.v = 5;
+ if level > 5 {
+ p.diag(warning!(start .. p.end(), "should not exceed depth 6"));
+ level = 5;
}
// Parse the heading contents.
let mut contents = vec![];
while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) {
- if let Some(node) = p.span_if(|p| node(p, &mut false)) {
- contents.push(node);
- }
+ contents.extend(node(p, &mut false));
}
Node::Heading(NodeHeading { level, contents })
@@ -152,7 +149,7 @@ fn heading(p: &mut Parser) -> Node {
/// Handle a raw block.
fn raw(p: &mut Parser, token: TokenRaw) -> Node {
- let raw = resolve::resolve_raw(token.text, token.backticks);
+ let raw = resolve::resolve_raw(token.text, token.backticks, p.start());
if !token.terminated {
p.diag(error!(p.peek_span().end, "expected backtick(s)"));
}
@@ -183,10 +180,9 @@ fn bracket_call(p: &mut Parser) -> Option<Expr> {
// One header is guaranteed, but there may be more (through chaining).
let mut outer = vec![];
- let mut inner = p.span_if(bracket_subheader);
-
+ let mut inner = bracket_subheader(p);
while p.eat_if(Token::Pipe) {
- if let Some(new) = p.span_if(bracket_subheader) {
+ if let Some(new) = bracket_subheader(p) {
outer.extend(inner);
inner = Some(new);
}
@@ -194,49 +190,44 @@ fn bracket_call(p: &mut Parser) -> Option<Expr> {
p.end_group();
- let body = if p.peek() == Some(Token::LeftBracket) {
- Some(p.span(|p| Expr::Template(bracket_body(p))))
- } else {
- None
+ let body = match p.peek() {
+ Some(Token::LeftBracket) => Some(bracket_body(p)),
+ _ => None,
};
let mut inner = inner?;
if let Some(body) = body {
- inner.span.expand(body.span);
- inner.v.args.v.push(Argument::Pos(body));
+ inner.span.expand(body.span());
+ inner.args.items.push(Argument::Pos(body));
}
while let Some(mut top) = outer.pop() {
- let span = inner.span;
- let node = inner.map(|c| Node::Expr(Expr::Call(c)));
- let expr = Expr::Template(vec![node]).with_span(span);
- top.v.args.v.push(Argument::Pos(expr));
+ top.args.items.push(Argument::Pos(Expr::Call(inner)));
inner = top;
}
- Some(Expr::Call(inner.v))
+ Some(Expr::Call(inner))
}
/// Parse one subheader of a bracketed function call.
fn bracket_subheader(p: &mut Parser) -> Option<ExprCall> {
p.start_group(Group::Subheader, TokenMode::Code);
-
- let name = p.span_if(ident);
- let args = p.span(arguments);
- p.end_group();
-
+ let name = ident(p);
+ let args = args(p);
+ let span = p.end_group();
Some(ExprCall {
- callee: Box::new(name?.map(Expr::Ident)),
+ span,
+ callee: Box::new(Expr::Ident(name?)),
args,
})
}
/// Parse the body of a bracketed function call.
-fn bracket_body(p: &mut Parser) -> Tree {
+fn bracket_body(p: &mut Parser) -> Expr {
p.start_group(Group::Bracket, TokenMode::Markup);
- let tree = tree(p);
- p.end_group();
- tree
+ let tree = Rc::new(tree(p));
+ let span = p.end_group();
+ Expr::Template(ExprTemplate { span, tree })
}
/// Parse an expression.
@@ -246,15 +237,14 @@ fn expr(p: &mut Parser) -> Option<Expr> {
/// Parse an expression with operators having at least the minimum precedence.
fn expr_with(p: &mut Parser, min_prec: usize) -> Option<Expr> {
- let mut lhs = match p.span_if(|p| p.eat_map(UnOp::from_token)) {
+ let start = p.start();
+ let mut lhs = match p.eat_map(UnOp::from_token) {
Some(op) => {
- let prec = op.v.precedence();
- let expr = p.span_if(|p| expr_with(p, prec))?;
- let span = op.span.join(expr.span);
- let unary = Expr::Unary(ExprUnary { op, expr: Box::new(expr) });
- unary.with_span(span)
+ let prec = op.precedence();
+ let expr = Box::new(expr_with(p, prec)?);
+ Expr::Unary(ExprUnary { span: p.span_from(start), op, expr })
}
- None => p.span_if(primary)?,
+ None => primary(p)?,
};
loop {
@@ -268,95 +258,87 @@ fn expr_with(p: &mut Parser, min_prec: usize) -> Option<Expr> {
break;
}
+ p.eat();
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)) {
+ let rhs = match 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);
+ let span = lhs.span().join(rhs.span());
+ lhs = Expr::Binary(ExprBinary { span, lhs: Box::new(lhs), op, rhs });
}
- Some(lhs.v)
+ Some(lhs)
}
/// Parse a primary expression.
fn primary(p: &mut Parser) -> Option<Expr> {
- let expr = match p.peek() {
- // Basic values.
- Some(Token::None) => Expr::None,
- Some(Token::Bool(b)) => Expr::Bool(b),
- Some(Token::Int(i)) => Expr::Int(i),
- Some(Token::Float(f)) => Expr::Float(f),
- Some(Token::Length(val, unit)) => Expr::Length(val, unit),
- Some(Token::Angle(val, unit)) => Expr::Angle(val, unit),
- Some(Token::Percent(p)) => Expr::Percent(p),
- Some(Token::Color(color)) => Expr::Color(color),
- Some(Token::Str(token)) => Expr::Str(string(p, token)),
+ if let Some(expr) = literal(p) {
+ return Some(expr);
+ }
+ match p.peek() {
// Function or identifier.
- Some(Token::Ident(id)) => {
- p.eat();
- let ident = Ident(id.into());
+ Some(Token::Ident(string)) => {
+ let ident = Ident {
+ span: p.eat_span(),
+ string: string.into(),
+ };
if p.peek() == Some(Token::LeftParen) {
- let name = ident.with_span(p.peek_span());
- return Some(paren_call(p, name));
+ Some(paren_call(p, ident))
} else {
- return Some(Expr::Ident(ident));
+ Some(Expr::Ident(ident))
}
}
// Keywords.
- Some(Token::Let) => return expr_let(p),
- Some(Token::If) => return expr_if(p),
- Some(Token::For) => return expr_for(p),
-
- // Block.
- Some(Token::LeftBrace) => {
- return block(p, true);
- }
-
- // Template.
- Some(Token::LeftBracket) => {
- return Some(template(p));
- }
+ Some(Token::Let) => expr_let(p),
+ Some(Token::If) => expr_if(p),
+ Some(Token::For) => expr_for(p),
- // Function template.
- Some(Token::HashBracket) => {
- let call = p.span_if(bracket_call)?.map(Node::Expr);
- return Some(Expr::Template(vec![call]));
- }
-
- // Array, dictionary or parenthesized expression.
- Some(Token::LeftParen) => {
- return Some(parenthesized(p));
- }
+ // Structures.
+ Some(Token::LeftBrace) => block(p, true),
+ Some(Token::LeftBracket) => Some(template(p)),
+ Some(Token::HashBracket) => bracket_call(p),
+ Some(Token::LeftParen) => Some(parenthesized(p)),
// Nothing.
_ => {
p.expected("expression");
- return None;
+ None
}
+ }
+}
+
+/// Parse a literal.
+fn literal(p: &mut Parser) -> Option<Expr> {
+ let kind = match p.peek()? {
+ // Basic values.
+ Token::None => LitKind::None,
+ Token::Bool(b) => LitKind::Bool(b),
+ Token::Int(i) => LitKind::Int(i),
+ Token::Float(f) => LitKind::Float(f),
+ Token::Length(val, unit) => LitKind::Length(val, unit),
+ Token::Angle(val, unit) => LitKind::Angle(val, unit),
+ Token::Percent(p) => LitKind::Percent(p),
+ Token::Color(color) => LitKind::Color(color),
+ Token::Str(token) => LitKind::Str(string(p, token)),
+ _ => return None,
};
- p.eat();
- Some(expr)
+ Some(Expr::Lit(Lit { span: p.eat_span(), kind }))
}
// Parse a template value: `[...]`.
fn template(p: &mut Parser) -> Expr {
p.start_group(Group::Bracket, TokenMode::Markup);
- let tree = tree(p);
- p.end_group();
- Expr::Template(tree)
+ let tree = Rc::new(tree(p));
+ let span = p.end_group();
+ Expr::Template(ExprTemplate { span, tree })
}
/// Parse a block expression: `{...}`.
@@ -365,26 +347,27 @@ fn block(p: &mut Parser, scopes: bool) -> Option<Expr> {
let mut exprs = vec![];
while !p.eof() {
p.start_group(Group::Stmt, TokenMode::Code);
- if let Some(expr) = p.span_if(expr) {
+ if let Some(expr) = expr(p) {
exprs.push(expr);
if !p.eof() {
- p.expected_at("semicolon or line break", p.last_end());
+ p.expected_at("semicolon or line break", p.end());
}
}
p.end_group();
p.skip_white();
}
- p.end_group();
- Some(Expr::Block(ExprBlock { exprs, scopes }))
+ let span = p.end_group();
+ Some(Expr::Block(ExprBlock { span, exprs, scoping: scopes }))
}
/// Parse a parenthesized function call.
-fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Expr {
+fn paren_call(p: &mut Parser, name: Ident) -> Expr {
p.start_group(Group::Paren, TokenMode::Code);
- let args = p.span(arguments);
+ let args = args(p);
p.end_group();
Expr::Call(ExprCall {
- callee: Box::new(name.map(Expr::Ident)),
+ span: p.span_from(name.span.start),
+ callee: Box::new(Expr::Ident(name)),
args,
})
}
@@ -394,22 +377,26 @@ fn string(p: &mut Parser, token: TokenStr) -> String {
if !token.terminated {
p.expected_at("quote", p.peek_span().end);
}
-
resolve::resolve_string(token.string)
}
/// Parse a let expression.
fn expr_let(p: &mut Parser) -> Option<Expr> {
+ let start = p.start();
p.assert(&[Token::Let]);
let mut expr_let = None;
- if let Some(pat) = p.span_if(ident) {
+ if let Some(binding) = ident(p) {
let mut init = None;
if p.eat_if(Token::Eq) {
- init = p.span_if(expr);
+ init = expr(p);
}
- expr_let = Some(Expr::Let(ExprLet { pat, init: init.map(Box::new) }))
+ expr_let = Some(Expr::Let(ExprLet {
+ span: p.span_from(start),
+ binding,
+ init: init.map(Box::new),
+ }))
}
expr_let
@@ -417,17 +404,19 @@ fn expr_let(p: &mut Parser) -> Option<Expr> {
/// Parse an if expresion.
fn expr_if(p: &mut Parser) -> Option<Expr> {
+ let start = p.start();
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) {
+ if let Some(condition) = expr(p) {
+ if let Some(if_body) = body(p) {
let mut else_body = None;
if p.eat_if(Token::Else) {
- else_body = p.span_if(body);
+ else_body = body(p);
}
expr_if = Some(Expr::If(ExprIf {
+ span: p.span_from(start),
condition: Box::new(condition),
if_body: Box::new(if_body),
else_body: else_body.map(Box::new),
@@ -440,15 +429,17 @@ fn expr_if(p: &mut Parser) -> Option<Expr> {
/// Parse a for expression.
fn expr_for(p: &mut Parser) -> Option<Expr> {
+ let start = p.start();
p.assert(&[Token::For]);
let mut expr_for = None;
- if let Some(pat) = p.span_if(for_pattern) {
+ if let Some(pattern) = for_pattern(p) {
if p.expect(Token::In) {
- if let Some(iter) = p.span_if(expr) {
- if let Some(body) = p.span_if(body) {
+ if let Some(iter) = expr(p) {
+ if let Some(body) = body(p) {
expr_for = Some(Expr::For(ExprFor {
- pat,
+ span: p.span_from(start),
+ pattern,
iter: Box::new(iter),
body: Box::new(body),
}));
@@ -473,15 +464,14 @@ fn for_pattern(p: &mut Parser) -> Option<ForPattern> {
/// 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
- }
+ if let Some(Token::Ident(string)) = p.peek() {
+ Some(Ident {
+ span: p.eat_span(),
+ string: string.to_string(),
+ })
+ } else {
+ p.expected("identifier");
+ None
}
}
@@ -491,7 +481,7 @@ fn body(p: &mut Parser) -> Option<Expr> {
Some(Token::LeftBracket) => Some(template(p)),
Some(Token::LeftBrace) => block(p, true),
_ => {
- p.expected_at("body", p.last_end());
+ p.expected_at("body", p.end());
None
}
}
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
index 986a36b0..a64e39dd 100644
--- a/src/parse/parser.rs
+++ b/src/parse/parser.rs
@@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter};
use super::{Scanner, TokenMode, Tokens};
use crate::diag::Diag;
use crate::diag::{Deco, Feedback};
-use crate::syntax::{Pos, Span, Spanned, Token, WithSpan};
+use crate::syntax::{Pos, Span, Spanned, Token};
/// A convenient token-based parser.
pub struct Parser<'s> {
@@ -18,14 +18,43 @@ pub struct Parser<'s> {
next_start: Pos,
/// The end position of the last (non-whitespace if in code mode) token.
last_end: Pos,
- /// The stack of modes we were in.
- modes: Vec<TokenMode>,
/// The stack of open groups.
- groups: Vec<Group>,
+ groups: Vec<GroupEntry>,
/// Accumulated feedback.
feedback: Feedback,
}
+/// A logical group of tokens, e.g. `[...]`.
+struct GroupEntry {
+ /// The start position of the group. Used by `Parser::end_group` to return
+ /// The group's full span.
+ start: Pos,
+ /// The kind of group this is. This decides which tokens will end the group.
+ /// For example, a [`GroupKind::Paren`] will be ended by
+ /// [`Token::RightParen`].
+ kind: Group,
+ /// The mode the parser was in _before_ the group started.
+ prev_mode: TokenMode,
+}
+
+/// A group, confined by optional start and end delimiters.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Group {
+ /// A parenthesized group: `(...)`.
+ Paren,
+ /// A bracketed group: `[...]`.
+ Bracket,
+ /// A curly-braced group: `{...}`.
+ Brace,
+ /// A group ended by a chained subheader or a closing bracket:
+ /// `... >>`, `...]`.
+ Subheader,
+ /// A group ended by a semicolon or a line break: `;`, `\n`.
+ Stmt,
+ /// A group for a single expression. Not ended by something specific.
+ Expr,
+}
+
impl<'s> Parser<'s> {
/// Create a new parser for the source string.
pub fn new(src: &'s str) -> Self {
@@ -37,7 +66,6 @@ impl<'s> Parser<'s> {
peeked: next,
next_start: Pos::ZERO,
last_end: Pos::ZERO,
- modes: vec![],
groups: vec![],
feedback: Feedback::new(),
}
@@ -97,14 +125,17 @@ impl<'s> Parser<'s> {
///
/// # Panics
/// This panics if the next token does not start the given group.
- pub fn start_group(&mut self, group: Group, mode: TokenMode) {
- self.modes.push(self.tokens.mode());
- self.tokens.set_mode(mode);
+ pub fn start_group(&mut self, kind: Group, mode: TokenMode) {
+ self.groups.push(GroupEntry {
+ start: self.next_start,
+ kind,
+ prev_mode: self.tokens.mode(),
+ });
- self.groups.push(group);
+ self.tokens.set_mode(mode);
self.repeek();
- match group {
+ match kind {
Group::Paren => self.assert(&[Token::LeftParen]),
Group::Bracket => self.assert(&[Token::HashBracket, Token::LeftBracket]),
Group::Brace => self.assert(&[Token::LeftBrace]),
@@ -118,15 +149,16 @@ impl<'s> Parser<'s> {
///
/// # Panics
/// This panics if no group was started.
- pub fn end_group(&mut self) {
+ pub fn end_group(&mut self) -> Span {
let prev_mode = self.tokens.mode();
- self.tokens.set_mode(self.modes.pop().expect("no pushed mode"));
-
let group = self.groups.pop().expect("no started group");
+ self.tokens.set_mode(group.prev_mode);
self.repeek();
+ let mut rescan = self.tokens.mode() != prev_mode;
+
// Eat the end delimiter if there is one.
- if let Some((end, required)) = match group {
+ if let Some((end, required)) = match group.kind {
Group::Paren => Some((Token::RightParen, true)),
Group::Bracket => Some((Token::RightBracket, true)),
Group::Brace => Some((Token::RightBrace, true)),
@@ -137,37 +169,19 @@ impl<'s> Parser<'s> {
if self.next == Some(end) {
// Bump the delimeter and return. No need to rescan in this case.
self.bump();
- return;
+ rescan = false;
} else if required {
self.diag(error!(self.next_start, "expected {}", end.name()));
}
}
// Rescan the peeked token if the mode changed.
- if self.tokens.mode() != prev_mode {
+ if rescan {
self.tokens.jump(self.last_end);
self.bump();
}
- }
-
- /// Execute `f` and return the result alongside the span of everything `f`
- /// ate. Excludes leading and trailing whitespace in code mode.
- pub fn span<T, F>(&mut self, f: F) -> Spanned<T>
- where
- F: FnOnce(&mut Self) -> T,
- {
- let start = self.next_start;
- let output = f(self);
- let end = self.last_end;
- output.with_span(start .. end)
- }
- /// A version of [`span`](Self::span) that works better with options.
- pub fn span_if<T, F>(&mut self, f: F) -> Option<Spanned<T>>
- where
- F: FnOnce(&mut Self) -> Option<T>,
- {
- self.span(f).transpose()
+ Span::new(group.start, self.last_end)
}
/// Consume the next token.
@@ -200,6 +214,13 @@ impl<'s> Parser<'s> {
mapped
}
+ /// Eat the next token and return its span.
+ pub fn eat_span(&mut self) -> Span {
+ let start = self.next_start;
+ self.eat();
+ Span::new(start, self.last_end)
+ }
+
/// Consume the next token if it is the given one and produce an error if
/// not.
pub fn expect(&mut self, t: Token) -> bool {
@@ -264,17 +285,22 @@ impl<'s> Parser<'s> {
}
/// The position at which the next token starts.
- pub fn next_start(&self) -> Pos {
+ pub fn start(&self) -> Pos {
self.next_start
}
/// The position at which the last token ended.
///
/// Refers to the end of the last _non-whitespace_ token in code mode.
- pub fn last_end(&self) -> Pos {
+ pub fn end(&self) -> Pos {
self.last_end
}
+ /// The span from
+ pub fn span_from(&self, start: Pos) -> Span {
+ Span::new(start, self.last_end)
+ }
+
/// Jump to a position in the source string.
pub fn jump(&mut self, pos: Pos) {
self.tokens.jump(pos);
@@ -325,13 +351,14 @@ impl<'s> Parser<'s> {
None => return,
};
+ let inside = |x| self.kinds().any(|k| k == x);
match token {
- Token::RightParen if self.groups.contains(&Group::Paren) => {}
- 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::RightParen if inside(Group::Paren) => {}
+ Token::RightBracket if inside(Group::Bracket) => {}
+ Token::RightBrace if inside(Group::Brace) => {}
+ Token::Semicolon if inside(Group::Stmt) => {}
+ Token::Pipe if inside(Group::Subheader) => {}
Token::Space(n) if n >= 1 && self.in_line_group() => {}
- Token::Pipe if self.groups.contains(&Group::Subheader) => {}
_ => return,
}
@@ -340,7 +367,15 @@ impl<'s> Parser<'s> {
/// Whether the active group ends at a newline.
fn in_line_group(&self) -> bool {
- matches!(self.groups.last(), Some(&Group::Stmt) | Some(&Group::Expr))
+ matches!(
+ self.kinds().next_back(),
+ Some(Group::Stmt) | Some(Group::Expr)
+ )
+ }
+
+ /// The outer groups.
+ fn kinds(&self) -> impl DoubleEndedIterator<Item = Group> + '_ {
+ self.groups.iter().map(|group| group.kind)
}
}
@@ -350,21 +385,3 @@ impl Debug for Parser<'_> {
write!(f, "Parser({}|{})", s.eaten(), s.rest())
}
}
-
-/// A group, confined by optional start and end delimiters.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum Group {
- /// A parenthesized group: `(...)`.
- Paren,
- /// A bracketed group: `[...]`.
- Bracket,
- /// A curly-braced group: `{...}`.
- Brace,
- /// A group ended by a chained subheader or a closing bracket:
- /// `... >>`, `...]`.
- Subheader,
- /// A group ended by a semicolon or a line break: `;`, `\n`.
- Stmt,
- /// A group for a single expression. Not ended by something specific.
- Expr,
-}
diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs
index a5e831da..4592acbc 100644
--- a/src/parse/resolve.rs
+++ b/src/parse/resolve.rs
@@ -1,5 +1,5 @@
use super::{is_newline, Scanner};
-use crate::syntax::{Ident, NodeRaw};
+use crate::syntax::{Ident, NodeRaw, Offset, Pos};
/// Resolve all escape sequences in a string.
pub fn resolve_string(string: &str) -> String {
@@ -47,12 +47,12 @@ pub fn resolve_hex(sequence: &str) -> Option<char> {
}
/// Resolve the language tag and trims the raw text.
-pub fn resolve_raw(text: &str, backticks: usize) -> NodeRaw {
+pub fn resolve_raw(text: &str, backticks: usize, start: Pos) -> NodeRaw {
if backticks > 1 {
let (tag, inner) = split_at_lang_tag(text);
let (lines, had_newline) = trim_and_split_raw(inner);
NodeRaw {
- lang: Ident::new(tag),
+ lang: Ident::new(tag, start .. start.offset(tag.len())),
lines,
block: had_newline,
}
@@ -125,6 +125,7 @@ pub fn split_lines(text: &str) -> Vec<String> {
#[cfg(test)]
#[rustfmt::skip]
mod tests {
+ use crate::syntax::Span;
use super::*;
#[test]
@@ -173,11 +174,11 @@ mod tests {
lines: &[&str],
block: bool,
) {
- assert_eq!(resolve_raw(raw, backticks), NodeRaw {
- lang: lang.map(|id| Ident(id.into())),
+ Span::without_cmp(|| assert_eq!(resolve_raw(raw, backticks, Pos(0)), NodeRaw {
+ lang: lang.and_then(|id| Ident::new(id, 0)),
lines: lines.iter().map(ToString::to_string).collect(),
block,
- });
+ }));
}
// Just one backtick.
diff --git a/src/prelude.rs b/src/prelude.rs
index 9856a837..6bab80e6 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -3,9 +3,11 @@
pub use crate::diag::{Feedback, Pass};
#[doc(no_inline)]
pub use crate::eval::{
- Args, CastResult, Eval, EvalContext, Value, ValueAny, ValueArray, ValueDict,
- ValueTemplate,
+ Args, CastResult, Eval, EvalContext, TemplateAny, TemplateNode, Value, ValueAny,
+ ValueArray, ValueDict, ValueTemplate,
};
+#[doc(no_inline)]
+pub use crate::exec::{Exec, ExecContext};
pub use crate::geom::*;
#[doc(no_inline)]
pub use crate::layout::Node;
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index 3160e0e4..f431ba8d 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -1,3 +1,5 @@
+use std::rc::Rc;
+
use super::*;
use crate::color::RgbaColor;
use crate::geom::{AngularUnit, LengthUnit};
@@ -5,29 +7,10 @@ use crate::geom::{AngularUnit, LengthUnit};
/// An expression.
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
- /// The none literal: `none`.
- None,
- /// A identifier literal: `left`.
+ /// A literal.
+ Lit(Lit),
+ /// An identifier: `left`.
Ident(Ident),
- /// A boolean literal: `true`, `false`.
- Bool(bool),
- /// An integer literal: `120`.
- Int(i64),
- /// A floating-point literal: `1.2`, `10e-4`.
- Float(f64),
- /// A length literal: `12pt`, `3cm`.
- Length(f64, LengthUnit),
- /// An angle literal: `1.5rad`, `90deg`.
- Angle(f64, AngularUnit),
- /// A percent literal: `50%`.
- ///
- /// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
- /// corresponding [value](crate::geom::Relative).
- Percent(f64),
- /// A color literal: `#ffccee`.
- Color(RgbaColor),
- /// A string literal: `"hello!"`.
- Str(String),
/// An array expression: `(1, "hi", 12cm)`.
Array(ExprArray),
/// A dictionary expression: `(color: #f79143, pattern: dashed)`.
@@ -52,11 +35,92 @@ pub enum Expr {
For(ExprFor),
}
+impl Expr {
+ /// The source code location.
+ pub fn span(&self) -> Span {
+ match self {
+ Self::Lit(v) => v.span,
+ Self::Ident(v) => v.span,
+ Self::Array(v) => v.span,
+ Self::Dict(v) => v.span,
+ Self::Template(v) => v.span,
+ Self::Group(v) => v.span,
+ Self::Block(v) => v.span,
+ Self::Unary(v) => v.span,
+ Self::Binary(v) => v.span,
+ Self::Call(v) => v.span,
+ Self::Let(v) => v.span,
+ Self::If(v) => v.span,
+ Self::For(v) => v.span,
+ }
+ }
+}
+
impl Pretty for Expr {
fn pretty(&self, p: &mut Printer) {
match self {
- Self::None => p.push_str("none"),
+ Self::Lit(v) => v.pretty(p),
Self::Ident(v) => v.pretty(p),
+ Self::Array(v) => v.pretty(p),
+ Self::Dict(v) => v.pretty(p),
+ Self::Template(v) => v.pretty(p),
+ Self::Group(v) => v.pretty(p),
+ Self::Block(v) => v.pretty(p),
+ Self::Unary(v) => v.pretty(p),
+ Self::Binary(v) => v.pretty(p),
+ Self::Call(v) => v.pretty(p),
+ Self::Let(v) => v.pretty(p),
+ Self::If(v) => v.pretty(p),
+ Self::For(v) => v.pretty(p),
+ }
+ }
+}
+
+/// A literal.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Lit {
+ /// The source code location.
+ pub span: Span,
+ /// The kind of literal.
+ pub kind: LitKind,
+}
+
+impl Pretty for Lit {
+ fn pretty(&self, p: &mut Printer) {
+ self.kind.pretty(p);
+ }
+}
+
+/// A kind of literal.
+#[derive(Debug, Clone, PartialEq)]
+pub enum LitKind {
+ /// The none literal: `none`.
+ None,
+ /// A boolean literal: `true`, `false`.
+ Bool(bool),
+ /// An integer literal: `120`.
+ Int(i64),
+ /// A floating-point literal: `1.2`, `10e-4`.
+ Float(f64),
+ /// A length literal: `12pt`, `3cm`.
+ Length(f64, LengthUnit),
+ /// An angle literal: `1.5rad`, `90deg`.
+ Angle(f64, AngularUnit),
+ /// A percent literal: `50%`.
+ ///
+ /// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
+ /// corresponding [value](crate::geom::Relative).
+ Percent(f64),
+ /// A color literal: `#ffccee`.
+ Color(RgbaColor),
+ /// A string literal: `"hello!"`.
+ Str(String),
+}
+
+impl Pretty for LitKind {
+ fn pretty(&self, p: &mut Printer) {
+ match self {
+ Self::None => p.push_str("none"),
Self::Bool(v) => v.pretty(p),
Self::Int(v) => v.pretty(p),
Self::Float(v) => v.pretty(p),
@@ -71,33 +135,24 @@ impl Pretty for Expr {
}
Self::Color(v) => v.pretty(p),
Self::Str(v) => v.pretty(p),
- Self::Array(v) => v.pretty(p),
- Self::Dict(v) => v.pretty(p),
- Self::Template(v) => pretty_template(v, p),
- Self::Group(v) => {
- p.push('(');
- v.v.pretty(p);
- p.push(')');
- }
- Self::Block(v) => v.pretty(p),
- Self::Unary(v) => v.pretty(p),
- Self::Binary(v) => v.pretty(p),
- Self::Call(v) => v.pretty(p),
- Self::Let(v) => v.pretty(p),
- Self::If(v) => v.pretty(p),
- Self::For(v) => v.pretty(p),
}
}
}
/// An array expression: `(1, "hi", 12cm)`.
-pub type ExprArray = SpanVec<Expr>;
+#[derive(Debug, Clone, PartialEq)]
+pub struct ExprArray {
+ /// The source code location.
+ pub span: Span,
+ /// The entries of the array.
+ pub items: Vec<Expr>,
+}
impl Pretty for ExprArray {
fn pretty(&self, p: &mut Printer) {
p.push('(');
- p.join(self, ", ", |item, p| item.v.pretty(p));
- if self.len() == 1 {
+ p.join(&self.items, ", ", |item, p| item.pretty(p));
+ if self.items.len() == 1 {
p.push(',');
}
p.push(')');
@@ -105,15 +160,21 @@ impl Pretty for ExprArray {
}
/// A dictionary expression: `(color: #f79143, pattern: dashed)`.
-pub type ExprDict = Vec<Named>;
+#[derive(Debug, Clone, PartialEq)]
+pub struct ExprDict {
+ /// The source code location.
+ pub span: Span,
+ /// The named dictionary entries.
+ pub items: Vec<Named>,
+}
impl Pretty for ExprDict {
fn pretty(&self, p: &mut Printer) {
p.push('(');
- if self.is_empty() {
+ if self.items.is_empty() {
p.push(':');
} else {
- p.join(self, ", ", |named, p| named.pretty(p));
+ p.join(&self.items, ", ", |named, p| named.pretty(p));
}
p.push(')');
}
@@ -123,43 +184,73 @@ impl Pretty for ExprDict {
#[derive(Debug, Clone, PartialEq)]
pub struct Named {
/// The name: `pattern`.
- pub name: Spanned<Ident>,
+ pub name: Ident,
/// The right-hand side of the pair: `dashed`.
- pub expr: Spanned<Expr>,
+ pub expr: Expr,
+}
+
+impl Named {
+ /// The source code location.
+ pub fn span(&self) -> Span {
+ self.name.span.join(self.expr.span())
+ }
}
impl Pretty for Named {
fn pretty(&self, p: &mut Printer) {
- self.name.v.pretty(p);
+ self.name.pretty(p);
p.push_str(": ");
- self.expr.v.pretty(p);
+ self.expr.pretty(p);
}
}
/// A template expression: `[*Hi* there!]`.
-pub type ExprTemplate = Tree;
+#[derive(Debug, Clone, PartialEq)]
+pub struct ExprTemplate {
+ /// The source code location.
+ pub span: Span,
+ /// The contents of the template.
+ pub tree: Rc<Tree>,
+}
-/// Pretty print a template.
-pub fn pretty_template(template: &ExprTemplate, p: &mut Printer) {
- if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() {
- pretty_func_template(call, p, false)
- } else {
- p.push('[');
- template.pretty(p);
- p.push(']');
+impl Pretty for ExprTemplate {
+ fn pretty(&self, p: &mut Printer) {
+ if let [Node::Expr(Expr::Call(call))] = self.tree.as_slice() {
+ call.pretty_bracketed(p, false);
+ } else {
+ p.push('[');
+ self.tree.pretty(p);
+ p.push(']');
+ }
}
}
/// A grouped expression: `(1 + 2)`.
-pub type ExprGroup = SpanBox<Expr>;
+#[derive(Debug, Clone, PartialEq)]
+pub struct ExprGroup {
+ /// The source code location.
+ pub span: Span,
+ /// The wrapped expression.
+ pub expr: Box<Expr>,
+}
+
+impl Pretty for ExprGroup {
+ fn pretty(&self, p: &mut Printer) {
+ p.push('(');
+ self.expr.pretty(p);
+ p.push(')');
+ }
+}
/// A block expression: `{ #let x = 1; x + 2 }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBlock {
+ /// The source code location.
+ pub span: Span,
/// The list of expressions contained in the block.
- pub exprs: SpanVec<Expr>,
+ pub exprs: Vec<Expr>,
/// Whether the block should create a scope.
- pub scopes: bool,
+ pub scoping: bool,
}
impl Pretty for ExprBlock {
@@ -168,7 +259,7 @@ impl Pretty for ExprBlock {
if self.exprs.len() > 1 {
p.push(' ');
}
- p.join(&self.exprs, "; ", |expr, p| expr.v.pretty(p));
+ p.join(&self.exprs, "; ", |expr, p| expr.pretty(p));
if self.exprs.len() > 1 {
p.push(' ');
}
@@ -179,19 +270,21 @@ impl Pretty for ExprBlock {
/// A unary operation: `-x`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprUnary {
+ /// The source code location.
+ pub span: Span,
/// The operator: `-`.
- pub op: Spanned<UnOp>,
+ pub op: UnOp,
/// The expression to operator on: `x`.
- pub expr: SpanBox<Expr>,
+ pub expr: Box<Expr>,
}
impl Pretty for ExprUnary {
fn pretty(&self, p: &mut Printer) {
- self.op.v.pretty(p);
- if self.op.v == UnOp::Not {
+ self.op.pretty(p);
+ if self.op == UnOp::Not {
p.push(' ');
}
- self.expr.v.pretty(p);
+ self.expr.pretty(p);
}
}
@@ -244,21 +337,23 @@ impl Pretty for UnOp {
/// A binary operation: `a + b`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBinary {
+ /// The source code location.
+ pub span: Span,
/// The left-hand side of the operation: `a`.
- pub lhs: SpanBox<Expr>,
+ pub lhs: Box<Expr>,
/// The operator: `+`.
- pub op: Spanned<BinOp>,
+ pub op: BinOp,
/// The right-hand side of the operation: `b`.
- pub rhs: SpanBox<Expr>,
+ pub rhs: Box<Expr>,
}
impl Pretty for ExprBinary {
fn pretty(&self, p: &mut Printer) {
- self.lhs.v.pretty(p);
+ self.lhs.pretty(p);
p.push(' ');
- self.op.v.pretty(p);
+ self.op.pretty(p);
p.push(' ');
- self.rhs.v.pretty(p);
+ self.rhs.pretty(p);
}
}
@@ -407,71 +502,83 @@ pub enum Associativity {
/// An invocation of a function: `foo(...)`, `#[foo ...]`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprCall {
+ /// The source code location.
+ pub span: Span,
/// The callee of the function.
- pub callee: SpanBox<Expr>,
+ pub callee: Box<Expr>,
/// The arguments to the function.
- pub args: Spanned<ExprArgs>,
+ pub args: ExprArgs,
}
impl Pretty for ExprCall {
fn pretty(&self, p: &mut Printer) {
- self.callee.v.pretty(p);
+ self.callee.pretty(p);
p.push('(');
- self.args.v.pretty(p);
+ self.args.pretty(p);
p.push(')');
}
}
-/// Pretty print a function template, with body or chaining when possible.
-pub fn pretty_func_template(call: &ExprCall, p: &mut Printer, chained: bool) {
- if chained {
- p.push_str(" | ");
- } else {
- p.push_str("#[");
- }
+impl ExprCall {
+ /// Pretty print a function template, with body or chaining when possible.
+ pub fn pretty_bracketed(&self, p: &mut Printer, chained: bool) {
+ if chained {
+ p.push_str(" | ");
+ } else {
+ p.push_str("#[");
+ }
- // Function name.
- call.callee.v.pretty(p);
+ // Function name.
+ self.callee.pretty(p);
- // Find out whether this can be written with a body or as a chain.
- //
- // Example: Transforms "#[v [Hi]]" => "#[v][Hi]".
- if let [head @ .., Argument::Pos(Spanned { v: Expr::Template(template), .. })] =
- call.args.v.as_slice()
- {
- // Previous arguments.
- if !head.is_empty() {
- p.push(' ');
- p.join(head, ", ", |item, p| item.pretty(p));
- }
+ let mut write_args = |items: &[Argument]| {
+ if !items.is_empty() {
+ p.push(' ');
+ p.join(items, ", ", |item, p| item.pretty(p));
+ }
+ };
+
+ match self.args.items.as_slice() {
+ // This can written as a chain.
+ //
+ // Example: Transforms "#[v][[f]]" => "#[v | f]".
+ [head @ .., Argument::Pos(Expr::Call(call))] => {
+ write_args(head);
+ call.pretty_bracketed(p, true);
+ }
- // Find out whether this can written as a chain.
- //
- // Example: Transforms "#[v][[f]]" => "#[v | f]".
- if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() {
- return pretty_func_template(call, p, true);
- } else {
- p.push_str("][");
- template.pretty(p);
+ // This can be written with a body.
+ //
+ // Example: Transforms "#[v [Hi]]" => "#[v][Hi]".
+ [head @ .., Argument::Pos(Expr::Template(template))] => {
+ write_args(head);
+ p.push(']');
+ template.pretty(p);
+ }
+
+ items => {
+ write_args(items);
+ p.push(']');
+ }
}
- } else if !call.args.v.is_empty() {
- p.push(' ');
- call.args.v.pretty(p);
}
-
- // Either end of header or end of body.
- p.push(']');
}
/// The arguments to a function: `12, draw: false`.
///
/// In case of a bracketed invocation with a body, the body is _not_
/// included in the span for the sake of clearer error messages.
-pub type ExprArgs = Vec<Argument>;
+#[derive(Debug, Clone, PartialEq)]
+pub struct ExprArgs {
+ /// The source code location.
+ pub span: Span,
+ /// The positional and named arguments.
+ pub items: Vec<Argument>,
+}
-impl Pretty for Vec<Argument> {
+impl Pretty for ExprArgs {
fn pretty(&self, p: &mut Printer) {
- p.join(self, ", ", |item, p| item.pretty(p));
+ p.join(&self.items, ", ", |item, p| item.pretty(p));
}
}
@@ -479,15 +586,25 @@ impl Pretty for Vec<Argument> {
#[derive(Debug, Clone, PartialEq)]
pub enum Argument {
/// A positional arguments.
- Pos(Spanned<Expr>),
+ Pos(Expr),
/// A named argument.
Named(Named),
}
+impl Argument {
+ /// The source code location.
+ pub fn span(&self) -> Span {
+ match self {
+ Self::Pos(expr) => expr.span(),
+ Self::Named(named) => named.span(),
+ }
+ }
+}
+
impl Pretty for Argument {
fn pretty(&self, p: &mut Printer) {
match self {
- Self::Pos(expr) => expr.v.pretty(p),
+ Self::Pos(expr) => expr.pretty(p),
Self::Named(named) => named.pretty(p),
}
}
@@ -496,19 +613,21 @@ impl Pretty for Argument {
/// A let expression: `#let x = 1`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprLet {
- /// The pattern to assign to.
- pub pat: Spanned<Ident>,
+ /// The source code location.
+ pub span: Span,
+ /// The binding to assign to.
+ pub binding: Ident,
/// The expression the pattern is initialized with.
- pub init: Option<SpanBox<Expr>>,
+ pub init: Option<Box<Expr>>,
}
impl Pretty for ExprLet {
fn pretty(&self, p: &mut Printer) {
p.push_str("#let ");
- self.pat.v.pretty(p);
+ self.binding.pretty(p);
if let Some(init) = &self.init {
p.push_str(" = ");
- init.v.pretty(p);
+ init.pretty(p);
}
}
}
@@ -516,23 +635,25 @@ impl Pretty for ExprLet {
/// An if expression: `#if x { y } #else { z }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprIf {
+ /// The source code location.
+ pub span: Span,
/// The condition which selects the body to evaluate.
- pub condition: SpanBox<Expr>,
+ pub condition: Box<Expr>,
/// The expression to evaluate if the condition is true.
- pub if_body: SpanBox<Expr>,
+ pub if_body: Box<Expr>,
/// The expression to evaluate if the condition is false.
- pub else_body: Option<SpanBox<Expr>>,
+ pub else_body: Option<Box<Expr>>,
}
impl Pretty for ExprIf {
fn pretty(&self, p: &mut Printer) {
p.push_str("#if ");
- self.condition.v.pretty(p);
+ self.condition.pretty(p);
p.push(' ');
- self.if_body.v.pretty(p);
+ self.if_body.pretty(p);
if let Some(expr) = &self.else_body {
p.push_str(" #else ");
- expr.v.pretty(p);
+ expr.pretty(p);
}
}
}
@@ -540,22 +661,24 @@ impl Pretty for ExprIf {
/// A for expression: `#for x #in y { z }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprFor {
+ /// The source code location.
+ pub span: Span,
/// The pattern to assign to.
- pub pat: Spanned<ForPattern>,
+ pub pattern: ForPattern,
/// The expression to iterate over.
- pub iter: SpanBox<Expr>,
+ pub iter: Box<Expr>,
/// The expression to evaluate for each iteration.
- pub body: SpanBox<Expr>,
+ pub body: Box<Expr>,
}
impl Pretty for ExprFor {
fn pretty(&self, p: &mut Printer) {
p.push_str("#for ");
- self.pat.v.pretty(p);
+ self.pattern.pretty(p);
p.push_str(" #in ");
- self.iter.v.pretty(p);
+ self.iter.pretty(p);
p.push(' ');
- self.body.v.pretty(p);
+ self.body.pretty(p);
}
}
@@ -568,6 +691,16 @@ pub enum ForPattern {
KeyValue(Ident, Ident),
}
+impl ForPattern {
+ /// The source code location.
+ pub fn span(&self) -> Span {
+ match self {
+ Self::Value(v) => v.span,
+ Self::KeyValue(k, v) => k.span.join(v.span),
+ }
+ }
+}
+
impl Pretty for ForPattern {
fn pretty(&self, p: &mut Printer) {
match self {
diff --git a/src/syntax/ident.rs b/src/syntax/ident.rs
index c4cc19bc..731a2789 100644
--- a/src/syntax/ident.rs
+++ b/src/syntax/ident.rs
@@ -2,6 +2,7 @@ use std::ops::Deref;
use unicode_xid::UnicodeXID;
+use super::Span;
use crate::pretty::{Pretty, Printer};
/// An Unicode identifier with a few extra permissible characters.
@@ -12,13 +13,21 @@ use crate::pretty::{Pretty, Printer};
///
/// [uax31]: http://www.unicode.org/reports/tr31/
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub struct Ident(pub String);
+pub struct Ident {
+ /// The source code location.
+ pub span: Span,
+ /// The identifier string.
+ pub string: String,
+}
impl Ident {
/// Create a new identifier from a string checking that it is a valid.
- pub fn new(ident: impl AsRef<str> + Into<String>) -> Option<Self> {
- if is_ident(ident.as_ref()) {
- Some(Self(ident.into()))
+ pub fn new(
+ string: impl AsRef<str> + Into<String>,
+ span: impl Into<Span>,
+ ) -> Option<Self> {
+ if is_ident(string.as_ref()) {
+ Some(Self { span: span.into(), string: string.into() })
} else {
None
}
@@ -26,19 +35,13 @@ impl Ident {
/// Return a reference to the underlying string.
pub fn as_str(&self) -> &str {
- self
- }
-}
-
-impl Pretty for Ident {
- fn pretty(&self, p: &mut Printer) {
- p.push_str(self.as_str());
+ self.string.as_str()
}
}
impl AsRef<str> for Ident {
fn as_ref(&self) -> &str {
- self
+ self.as_str()
}
}
@@ -46,7 +49,13 @@ impl Deref for Ident {
type Target = str;
fn deref(&self) -> &Self::Target {
- self.0.as_str()
+ self.as_str()
+ }
+}
+
+impl Pretty for Ident {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str(self.as_str());
}
}
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index 8bb6931a..a8ed2457 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -16,12 +16,12 @@ pub use token::*;
use crate::pretty::{Pretty, Printer};
/// The abstract syntax tree.
-pub type Tree = SpanVec<Node>;
+pub type Tree = Vec<Node>;
impl Pretty for Tree {
fn pretty(&self, p: &mut Printer) {
for node in self {
- node.v.pretty(p);
+ node.pretty(p);
}
}
}
@@ -133,9 +133,8 @@ mod tests {
roundtrip("#[v 1]");
roundtrip("#[v 1, 2][*Ok*]");
roundtrip("#[v 1 | f 2]");
- roundtrip("{#[v]}");
+ test("{#[v]}", "{v()}");
test("#[v 1, #[f 2]]", "#[v 1 | f 2]");
- test("#[v 1, 2][#[f 3]]", "#[v 1, 2 | f 3]");
// Keywords.
roundtrip("#let x = 1 + 2");
diff --git a/src/syntax/node.rs b/src/syntax/node.rs
index b4866068..fe9767a1 100644
--- a/src/syntax/node.rs
+++ b/src/syntax/node.rs
@@ -37,7 +37,7 @@ impl Pretty for Node {
Self::Expr(expr) => {
if let Expr::Call(call) = expr {
// Format function templates appropriately.
- pretty_func_template(call, p, false)
+ call.pretty_bracketed(p, false)
} else {
expr.pretty(p);
}
@@ -49,15 +49,15 @@ impl Pretty for Node {
/// A section heading: `= Introduction`.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeHeading {
- /// The section depth (numer of equals signs minus 1, capped at 5).
- pub level: Spanned<u8>,
+ /// The section depth (numer of equals signs minus 1).
+ pub level: usize,
/// The contents of the heading.
pub contents: Tree,
}
impl Pretty for NodeHeading {
fn pretty(&self, p: &mut Printer) {
- for _ in 0 ..= self.level.v {
+ for _ in 0 ..= self.level {
p.push('=');
}
self.contents.pretty(p);
diff --git a/src/syntax/span.rs b/src/syntax/span.rs
index 5087dffa..65b1d637 100644
--- a/src/syntax/span.rs
+++ b/src/syntax/span.rs
@@ -19,14 +19,15 @@ impl<T> WithSpan for T {}
/// Span offsetting.
pub trait Offset {
/// Offset all spans contained in `Self` by the given position.
- fn offset(self, by: Pos) -> Self;
+ fn offset(self, by: impl Into<Pos>) -> Self;
}
/// A vector of spanned values of type `T`.
pub type SpanVec<T> = Vec<Spanned<T>>;
impl<T> Offset for SpanVec<T> {
- fn offset(mut self, by: Pos) -> Self {
+ fn offset(mut self, by: impl Into<Pos>) -> Self {
+ let by = by.into();
for spanned in &mut self {
spanned.span = spanned.span.offset(by);
}
@@ -34,9 +35,6 @@ 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))]
@@ -90,7 +88,7 @@ impl<T> Spanned<Option<T>> {
}
impl<T> Offset for Spanned<T> {
- fn offset(self, by: Pos) -> Self {
+ fn offset(self, by: impl Into<Pos>) -> Self {
self.map_span(|span| span.offset(by))
}
}
@@ -174,7 +172,8 @@ impl Span {
}
impl Offset for Span {
- fn offset(self, by: Pos) -> Self {
+ fn offset(self, by: impl Into<Pos>) -> Self {
+ let by = by.into();
Self {
start: self.start.offset(by),
end: self.end.offset(by),
@@ -236,8 +235,8 @@ impl Pos {
}
impl Offset for Pos {
- fn offset(self, by: Self) -> Self {
- Pos(self.0 + by.0)
+ fn offset(self, by: impl Into<Pos>) -> Self {
+ Pos(self.0 + by.into().0)
}
}
diff --git a/src/syntax/token.rs b/src/syntax/token.rs
index 5e69a350..411e835f 100644
--- a/src/syntax/token.rs
+++ b/src/syntax/token.rs
@@ -123,7 +123,7 @@ pub enum Token<'s> {
/// A percentage: `50%`.
///
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
- /// [literal](super::Expr::Percent).
+ /// [literal](super::LitKind::Percent).
Percent(f64),
/// A color value: `#20d82a`.
Color(RgbaColor),
diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs
index 3a289eb1..70159d2d 100644
--- a/src/syntax/visit.rs
+++ b/src/syntax/visit.rs
@@ -3,17 +3,17 @@
use super::*;
macro_rules! visit {
- ($(fn $name:ident($v:ident, $item:ident: &$ty:ty) $body:block)*) => {
+ ($(fn $name:ident($v:ident, $node:ident: &$ty:ty) $body:block)*) => {
/// Traverses the syntax tree.
pub trait Visit<'ast> {
- $(fn $name(&mut self, $item: &'ast $ty) {
- $name(self, $item);
+ $(fn $name(&mut self, $node: &'ast $ty) {
+ $name(self, $node);
})*
}
$(visit! {
@concat!("Walk a node of type [`", stringify!($ty), "`]."),
- pub fn $name<'ast, V>($v: &mut V, $item: &'ast $ty)
+ pub fn $name<'ast, V>($v: &mut V, $node: &'ast $ty)
where
V: Visit<'ast> + ?Sized
$body
@@ -27,14 +27,14 @@ macro_rules! visit {
}
visit! {
- fn visit_tree(v, item: &Tree) {
- for node in item {
- v.visit_node(&node.v);
+ fn visit_tree(v, node: &Tree) {
+ for node in node {
+ v.visit_node(&node);
}
}
- fn visit_node(v, item: &Node) {
- match item {
+ fn visit_node(v, node: &Node) {
+ match node {
Node::Strong => {}
Node::Emph => {}
Node::Space => {}
@@ -47,18 +47,10 @@ visit! {
}
}
- fn visit_expr(v, item: &Expr) {
- match item {
- Expr::None => {}
+ fn visit_expr(v, node: &Expr) {
+ match node {
+ Expr::Lit(_) => {}
Expr::Ident(_) => {}
- Expr::Bool(_) => {}
- Expr::Int(_) => {}
- Expr::Float(_) => {}
- Expr::Length(_, _) => {}
- Expr::Angle(_, _) => {}
- Expr::Percent(_) => {}
- Expr::Color(_) => {}
- Expr::Str(_) => {}
Expr::Array(e) => v.visit_array(e),
Expr::Dict(e) => v.visit_dict(e),
Expr::Template(e) => v.visit_template(e),
@@ -73,75 +65,75 @@ visit! {
}
}
- fn visit_array(v, item: &ExprArray) {
- for expr in item {
- v.visit_expr(&expr.v);
+ fn visit_array(v, node: &ExprArray) {
+ for expr in &node.items {
+ v.visit_expr(&expr);
}
}
- fn visit_dict(v, item: &ExprDict) {
- for named in item {
- v.visit_expr(&named.expr.v);
+ fn visit_dict(v, node: &ExprDict) {
+ for named in &node.items {
+ v.visit_expr(&named.expr);
}
}
- fn visit_template(v, item: &ExprTemplate) {
- v.visit_tree(item);
+ fn visit_template(v, node: &ExprTemplate) {
+ v.visit_tree(&node.tree);
}
- fn visit_group(v, item: &ExprGroup) {
- v.visit_expr(&item.v);
+ fn visit_group(v, node: &ExprGroup) {
+ v.visit_expr(&node.expr);
}
- fn visit_block(v, item: &ExprBlock) {
- for expr in &item.exprs {
- v.visit_expr(&expr.v);
+ fn visit_block(v, node: &ExprBlock) {
+ for expr in &node.exprs {
+ v.visit_expr(&expr);
}
}
- fn visit_binary(v, item: &ExprBinary) {
- v.visit_expr(&item.lhs.v);
- v.visit_expr(&item.rhs.v);
+ fn visit_binary(v, node: &ExprBinary) {
+ v.visit_expr(&node.lhs);
+ v.visit_expr(&node.rhs);
}
- fn visit_unary(v, item: &ExprUnary) {
- v.visit_expr(&item.expr.v);
+ fn visit_unary(v, node: &ExprUnary) {
+ v.visit_expr(&node.expr);
}
- fn visit_call(v, item: &ExprCall) {
- v.visit_expr(&item.callee.v);
- v.visit_args(&item.args.v);
+ fn visit_call(v, node: &ExprCall) {
+ v.visit_expr(&node.callee);
+ v.visit_args(&node.args);
}
- fn visit_args(v, item: &ExprArgs) {
- for arg in item {
+ fn visit_args(v, node: &ExprArgs) {
+ for arg in &node.items {
v.visit_arg(arg);
}
}
- fn visit_arg(v, item: &Argument) {
- match item {
- Argument::Pos(expr) => v.visit_expr(&expr.v),
- Argument::Named(named) => v.visit_expr(&named.expr.v),
+ fn visit_arg(v, node: &Argument) {
+ match node {
+ Argument::Pos(expr) => v.visit_expr(&expr),
+ Argument::Named(named) => v.visit_expr(&named.expr),
}
}
- fn visit_let(v, item: &ExprLet) {
- if let Some(init) = &item.init {
- v.visit_expr(&init.v);
+ fn visit_let(v, node: &ExprLet) {
+ if let Some(init) = &node.init {
+ v.visit_expr(&init);
}
}
- fn visit_if(v, item: &ExprIf) {
- v.visit_expr(&item.condition.v);
- v.visit_expr(&item.if_body.v);
- if let Some(body) = &item.else_body {
- v.visit_expr(&body.v);
+ fn visit_if(v, node: &ExprIf) {
+ v.visit_expr(&node.condition);
+ v.visit_expr(&node.if_body);
+ if let Some(body) = &node.else_body {
+ v.visit_expr(&body);
}
}
- fn visit_for(v, item: &ExprFor) {
- v.visit_expr(&item.iter.v);
- v.visit_expr(&item.body.v);
+ fn visit_for(v, node: &ExprFor) {
+ v.visit_expr(&node.iter);
+ v.visit_expr(&node.body);
}
}