Split evaluation and execution 🔪

This commit is contained in:
Laurenz 2021-02-09 19:46:57 +01:00
parent e35bbfffcb
commit 06ca740d01
31 changed files with 1403 additions and 1019 deletions

View File

@ -2,7 +2,8 @@ use criterion::{criterion_group, criterion_main, Criterion};
use fontdock::fs::FsIndex;
use typst::env::{Env, ResourceLoader};
use typst::eval::{eval, State};
use typst::eval::eval;
use typst::exec::{exec, State};
use typst::export::pdf;
use typst::font::FsIndexExt;
use typst::layout::layout;
@ -33,14 +34,16 @@ fn benchmarks(c: &mut Criterion) {
// Prepare intermediate results and run warm.
let syntax_tree = parse(COMA).output;
let layout_tree = eval(&syntax_tree, &mut env, &scope, state.clone()).output;
let frames = layout(&layout_tree, &mut env);
let expr_map = eval(&mut env, &syntax_tree, &scope).output;
let layout_tree = exec(&mut env, &syntax_tree, &expr_map, state.clone()).output;
let frames = layout(&mut env, &layout_tree);
// Bench!
bench!("parse-coma": parse(COMA));
bench!("eval-coma": eval(&syntax_tree, &mut env, &scope, state.clone()));
bench!("layout-coma": layout(&layout_tree, &mut env));
bench!("typeset-coma": typeset(COMA, &mut env, &scope, state.clone()));
bench!("eval-coma": eval(&mut env, &syntax_tree, &scope));
bench!("exec-coma": exec(&mut env, &syntax_tree, &expr_map, state.clone()));
bench!("layout-coma": layout(&mut env, &layout_tree));
bench!("typeset-coma": typeset(&mut env, COMA, &scope, state.clone()));
bench!("export-pdf-coma": pdf::export(&frames, &env));
}

View File

@ -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);

View File

@ -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()),
));
}
}

View File

@ -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);

View File

@ -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.
/// 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> 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(),
}
}
/// Add a diagnostic to the feedback.
pub fn diag(&mut self, diag: Spanned<Diag>) {
self.feedback.diags.push(diag);
}
}
/// Evaluate an expression.
pub trait Eval {
/// The output of evaluating the item.
/// The output of evaluating the expression.
type Output;
/// Evaluate the item to the output value.
fn eval(self, ctx: &mut EvalContext) -> Self::Output;
/// Evaluate the expression to the output value.
fn eval(&self, ctx: &mut EvalContext) -> Self::Output;
}
impl<'a, T> Eval for &'a Spanned<T>
where
Spanned<&'a T>: Eval,
{
type Output = <Spanned<&'a T> as Eval>::Output;
impl Eval for Tree {
type Output = ExprMap;
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);
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
struct ExprVisitor<'a, 'b> {
map: ExprMap,
ctx: &'a mut EvalContext<'b>,
}
}
}
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)
impl<'ast> Visit<'ast> for ExprVisitor<'_, '_> {
fn visit_expr(&mut self, item: &'ast Expr) {
self.map.insert(item as *const _, item.eval(self.ctx));
}
}
let mut visitor = ExprVisitor { map: HashMap::new(), ctx };
visitor.visit_tree(self);
visitor.map
}
}
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();
ctx.state = prev;
}
}
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,
}));
}
if self.v.block {
ctx.apply_parbreak();
}
ctx.push(NodeStack {
dirs: ctx.state.dirs,
align: ctx.state.align,
expand: Spec::uniform(Expansion::Fit),
children,
});
if self.v.block {
ctx.apply_parbreak();
}
ctx.state.font.families = prev;
}
}
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 Spanned<&ExprArray> {
type Output = ValueArray;
impl Eval for Lit {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
self.v.iter().map(|expr| expr.eval(ctx)).collect()
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<&ExprDict> {
impl Eval for ExprArray {
type Output = ValueArray;
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
self.items.iter().map(|expr| expr.eval(ctx)).collect()
}
}
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> {
type Output = Value;
impl Eval for ExprTemplate {
type Output = TemplateNode;
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 {
let tree = Rc::clone(&self.tree);
let map = self.tree.eval(ctx);
TemplateNode::Tree { tree, map }
}
}
impl Eval for Spanned<&ExprBlock> {
impl Eval for ExprGroup {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
if self.v.scopes {
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
self.expr.eval(ctx)
}
}
impl Eval for ExprBlock {
type Output = Value;
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(),
));

View File

@ -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.

View File

@ -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)");
}
}

View File

@ -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.

161
src/exec/mod.rs Normal file
View File

@ -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);
}
}

View File

@ -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);

View File

@ -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 })
}

View File

@ -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)]

View File

@ -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)
}

View File

@ -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.

View File

@ -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 body = args.find::<ValueTemplate>(ctx);
let mut had = Gen::uniform(false);
let mut had_center = false;
Value::template(move |ctx| {
let snapshot = ctx.state.clone();
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);
let mut had = Gen::uniform(false);
let mut had_center = false;
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));
// 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 {
*ctx.state.align.get_mut(gen_axis) = gen_align;
*had.get_mut(gen_axis) = 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;
}
}
} 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);
} 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;
}
had_center = false;
}
}
// 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;
// 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 `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 ctx.state.align.main != snapshot.align.main {
ctx.end_par_group();
ctx.start_par_group();
}
if let Some(body) = args.find::<ValueTemplate>(ctx) {
body.eval(ctx);
ctx.state = snapshot;
}
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));
Value::template(move |ctx| {
let snapshot = ctx.state.clone();
let dirs = ctx.state.dirs;
let align = ctx.state.align;
ctx.set_dirs(Gen::new(main, cross));
let dirs = ctx.state.dirs;
let align = ctx.state.align;
ctx.start_content_group();
ctx.start_content_group();
if let Some(body) = &body {
body.exec(ctx);
}
let children = ctx.end_content_group();
if let Some(body) = args.find::<ValueTemplate>(ctx) {
body.eval(ctx);
}
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(),
};
let children = ctx.end_content_group();
if let Some(color) = color {
ctx.push(NodeBackground {
fill: Fill::Color(color),
child: fixed.into(),
});
} else {
ctx.push(fixed);
}
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(),
};
if let Some(color) = color {
ctx.push(NodeBackground {
fill: Fill::Color(color),
child: fixed_node.into(),
})
} else {
ctx.push(fixed_node);
}
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);
if let Some(name) = args.find::<Spanned<String>>(ctx) {
if let Some(paper) = Paper::from_name(&name.v) {
Value::template(move |ctx| {
let snapshot = ctx.state.clone();
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(width) = 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(height) = height {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
ctx.state.page.expand.vertical = Expansion::Fill;
}
if let Some(margins) = args.get(ctx, "margins") {
ctx.state.page.margins = Sides::uniform(Some(margins));
}
if let Some(margins) = margins {
ctx.state.page.margins = Sides::uniform(Some(margins));
}
if let Some(left) = args.get(ctx, "left") {
ctx.state.page.margins.left = Some(left);
}
if let Some(left) = left {
ctx.state.page.margins.left = Some(left);
}
if let Some(top) = args.get(ctx, "top") {
ctx.state.page.margins.top = Some(top);
}
if let Some(top) = top {
ctx.state.page.margins.top = Some(top);
}
if let Some(right) = args.get(ctx, "right") {
ctx.state.page.margins.right = Some(right);
}
if let Some(right) = right {
ctx.state.page.margins.right = Some(right);
}
if let Some(bottom) = args.get(ctx, "bottom") {
ctx.state.page.margins.bottom = Some(bottom);
}
if let Some(bottom) = bottom {
ctx.state.page.margins.bottom = Some(bottom);
}
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 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);
}
let main = args.get(ctx, "main-dir");
let cross = args.get(ctx, "cross-dir");
ctx.set_dirs(Gen::new(main, cross));
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;
}
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;
}
ctx.start_page_group(softness);
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);
})
}

View File

@ -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);
if let Some(style) = args.get(ctx, "style") {
ctx.state.font.variant.style = style;
}
Value::template(move |ctx| {
let snapshot = ctx.state.clone();
if let Some(weight) = args.get(ctx, "weight") {
ctx.state.font.variant.weight = weight;
}
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(stretch) = args.get(ctx, "stretch") {
ctx.state.font.variant.stretch = stretch;
}
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();
if !list.is_empty() {
let families = ctx.state.font.families_mut();
families.update_class_list(variant.to_string(), strings);
families.list = list.clone();
families.flatten();
}
}
if let Some(body) = args.find::<ValueTemplate>(ctx) {
body.eval(ctx);
ctx.state = snapshot;
}
if let Some(style) = style {
ctx.state.font.variant.style = style;
}
Value::None
if let Some(weight) = weight {
ctx.state.font.variant.weight = weight;
}
if let Some(stretch) = stretch {
ctx.state.font.variant.stretch = stretch;
}
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();
}
}
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",
@ -145,6 +145,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())

View File

@ -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();

View File

@ -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",
}));

View File

@ -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 {
let start = p.start();
p.assert(&[Token::Eq]);
// Count depth.
let mut level = p.span(|p| {
p.assert(&[Token::Eq]);
let mut level: usize = 0;
while p.eat_if(Token::Eq) {
level += 1;
}
let mut level = 0u8;
while p.eat_if(Token::Eq) {
level = level.saturating_add(1);
}
level
});
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),
Some(Token::Let) => expr_let(p),
Some(Token::If) => expr_if(p),
Some(Token::For) => expr_for(p),
// Block.
Some(Token::LeftBrace) => {
return block(p, true);
}
// Template.
Some(Token::LeftBracket) => {
return Some(template(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
}
}

View File

@ -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,
}

View File

@ -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.

View File

@ -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;

View File

@ -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("#[");
}
// Function name.
call.callee.v.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));
}
// 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);
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("][");
template.pretty(p);
p.push_str("#[");
}
} else if !call.args.v.is_empty() {
p.push(' ');
call.args.v.pretty(p);
}
// Either end of header or end of body.
p.push(']');
// Function name.
self.callee.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);
}
// 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(']');
}
}
}
}
/// 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 {

View File

@ -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());
}
}

View File

@ -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");

View File

@ -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);

View File

@ -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)
}
}

View File

@ -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),

View File

@ -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);
}
}

View File

@ -16,7 +16,8 @@ use walkdir::WalkDir;
use typst::diag::{Diag, Feedback, Level, Pass};
use typst::env::{Env, ImageResource, ResourceLoader};
use typst::eval::{Args, EvalContext, Scope, State, Value, ValueFunc};
use typst::eval::{Args, EvalContext, Scope, Value, ValueFunc};
use typst::exec::State;
use typst::export::pdf;
use typst::font::FsIndexExt;
use typst::geom::{Length, Point, Sides, Size, Spec};
@ -222,7 +223,7 @@ fn test_part(
let Pass {
output: mut frames,
feedback: Feedback { mut diags, .. },
} = typeset(&src, env, &scope, state);
} = typeset(env, &src, &scope, state);
if !compare_ref {
frames.clear();