mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Split evaluation and execution 🔪
This commit is contained in:
parent
e35bbfffcb
commit
06ca740d01
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
380
src/eval/mod.rs
380
src/eval/mod.rs
@ -1,247 +1,199 @@
|
||||
//! Evaluation of syntax trees into layout trees.
|
||||
//! Evaluation of syntax trees.
|
||||
|
||||
#[macro_use]
|
||||
mod value;
|
||||
mod call;
|
||||
mod capture;
|
||||
mod context;
|
||||
mod ops;
|
||||
mod scope;
|
||||
mod state;
|
||||
|
||||
pub use call::*;
|
||||
pub use capture::*;
|
||||
pub use context::*;
|
||||
pub use scope::*;
|
||||
pub use state::*;
|
||||
pub use value::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
use crate::color::Color;
|
||||
use crate::diag::Pass;
|
||||
use crate::env::Env;
|
||||
use crate::geom::{Angle, Length, Relative, Spec};
|
||||
use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
|
||||
use crate::diag::{Diag, Feedback};
|
||||
use crate::geom::{Angle, Length, Relative};
|
||||
use crate::syntax::visit::Visit;
|
||||
use crate::syntax::*;
|
||||
|
||||
/// Evaluate a syntax tree into a layout tree.
|
||||
/// Evaluate all expressions in a syntax tree.
|
||||
///
|
||||
/// The `state` is the base state that may be updated over the course of
|
||||
/// evaluation. The `scope` similarly consists of the base definitions that are
|
||||
/// present from the beginning (typically, the standard library).
|
||||
pub fn eval(
|
||||
tree: &Tree,
|
||||
env: &mut Env,
|
||||
scope: &Scope,
|
||||
state: State,
|
||||
) -> Pass<layout::Tree> {
|
||||
let mut ctx = EvalContext::new(env, scope, state);
|
||||
ctx.start_page_group(Softness::Hard);
|
||||
tree.eval(&mut ctx);
|
||||
ctx.end_page_group(|s| s == Softness::Hard);
|
||||
ctx.finish()
|
||||
/// The `scope` consists of the base definitions that are present from the
|
||||
/// beginning (typically, the standard library).
|
||||
pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass<ExprMap> {
|
||||
let mut ctx = EvalContext::new(env, scope);
|
||||
let map = tree.eval(&mut ctx);
|
||||
Pass::new(map, ctx.feedback)
|
||||
}
|
||||
|
||||
/// Evaluate an item.
|
||||
/// A map from expression to values to evaluated to.
|
||||
///
|
||||
/// _Note_: Evaluation is not necessarily pure, it may change the active state.
|
||||
/// 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(),
|
||||
));
|
||||
|
@ -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.
|
||||
|
@ -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)");
|
||||
}
|
||||
}
|
||||
|
@ -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
161
src/exec/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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 })
|
||||
}
|
||||
|
||||
|
@ -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)]
|
||||
|
35
src/lib.rs
35
src/lib.rs
@ -5,10 +5,13 @@
|
||||
//! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax
|
||||
//! tree]. The structures describing the tree can be found in the [syntax]
|
||||
//! module.
|
||||
//! - **Evaluation:** The next step is to [evaluate] the parsed "script" into a
|
||||
//! [layout tree], a high-level, fully styled representation. The nodes of
|
||||
//! this tree are fully self-contained and order-independent and thus much
|
||||
//! better suited for layouting than the syntax tree.
|
||||
//! - **Evaluation:** The next step is to [evaluate] the syntax tree. This
|
||||
//! computes the value of each expression in document and stores them in a map
|
||||
//! from expression-pointers to values.
|
||||
//! - **Execution:** Now, we can [execute] the parsed and evaluated "script".
|
||||
//! This produces a [layout tree], a high-level, fully styled representation.
|
||||
//! The nodes of this tree are self-contained and order-independent and thus
|
||||
//! much better suited for layouting than the syntax tree.
|
||||
//! - **Layouting:** Next, the tree is to [layouted] into a portable version of
|
||||
//! the typeset document. The output of this is a vector of [`Frame`]s
|
||||
//! (corresponding to pages), ready for exporting.
|
||||
@ -20,6 +23,7 @@
|
||||
//! [parsed]: parse::parse
|
||||
//! [syntax tree]: syntax::Tree
|
||||
//! [evaluate]: eval::eval
|
||||
//! [execute]: exec::exec
|
||||
//! [layout tree]: layout::Tree
|
||||
//! [layouted]: layout::layout
|
||||
//! [_PDF_]: export::pdf
|
||||
@ -30,6 +34,7 @@ pub mod diag;
|
||||
pub mod eval;
|
||||
pub mod color;
|
||||
pub mod env;
|
||||
pub mod exec;
|
||||
pub mod export;
|
||||
pub mod font;
|
||||
pub mod geom;
|
||||
@ -42,21 +47,27 @@ pub mod pretty;
|
||||
pub mod shaping;
|
||||
pub mod syntax;
|
||||
|
||||
use crate::diag::{Feedback, Pass};
|
||||
use crate::diag::Pass;
|
||||
use crate::env::Env;
|
||||
use crate::eval::{Scope, State};
|
||||
use crate::eval::Scope;
|
||||
use crate::exec::State;
|
||||
use crate::layout::Frame;
|
||||
|
||||
/// Process _Typst_ source code directly into a collection of frames.
|
||||
pub fn typeset(
|
||||
src: &str,
|
||||
env: &mut Env,
|
||||
src: &str,
|
||||
scope: &Scope,
|
||||
state: State,
|
||||
) -> Pass<Vec<Frame>> {
|
||||
let Pass { output: syntax_tree, feedback: f1 } = parse::parse(src);
|
||||
let Pass { output: layout_tree, feedback: f2 } =
|
||||
eval::eval(&syntax_tree, env, scope, state);
|
||||
let frames = layout::layout(&layout_tree, env);
|
||||
Pass::new(frames, Feedback::join(f1, f2))
|
||||
let parsed = parse::parse(src);
|
||||
let evaluated = eval::eval(env, &parsed.output, scope);
|
||||
let executed = exec::exec(env, &parsed.output, &evaluated.output, state);
|
||||
let frames = layout::layout(env, &executed.output);
|
||||
|
||||
let mut feedback = parsed.feedback;
|
||||
feedback.extend(evaluated.feedback);
|
||||
feedback.extend(executed.feedback);
|
||||
|
||||
Pass::new(frames, feedback)
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
})
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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();
|
||||
|
@ -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",
|
||||
}));
|
||||
|
260
src/parse/mod.rs
260
src/parse/mod.rs
@ -13,10 +13,11 @@ pub use resolve::*;
|
||||
pub use scanner::*;
|
||||
pub use tokens::*;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::diag::Pass;
|
||||
use crate::syntax::*;
|
||||
|
||||
use collection::{arguments, parenthesized};
|
||||
use collection::{args, parenthesized};
|
||||
|
||||
/// Parse a string of source code.
|
||||
pub fn parse(src: &str) -> Pass<Tree> {
|
||||
@ -31,8 +32,8 @@ fn tree(p: &mut Parser) -> Tree {
|
||||
let mut at_start = true;
|
||||
let mut tree = vec![];
|
||||
while !p.eof() {
|
||||
if let Some(node) = p.span_if(|p| node(p, &mut at_start)) {
|
||||
if !matches!(node.v, Node::Parbreak | Node::Space) {
|
||||
if let Some(node) = node(p, &mut at_start) {
|
||||
if !matches!(node, Node::Parbreak | Node::Space) {
|
||||
at_start = false;
|
||||
}
|
||||
tree.push(node);
|
||||
@ -78,12 +79,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
p.start_group(group, TokenMode::Code);
|
||||
let expr = primary(p);
|
||||
if stmt && expr.is_some() && !p.eof() {
|
||||
p.expected_at("semicolon or line break", p.last_end());
|
||||
p.expected_at("semicolon or line break", p.end());
|
||||
}
|
||||
p.end_group();
|
||||
|
||||
// Uneat spaces we might have eaten eagerly.
|
||||
p.jump(p.last_end());
|
||||
p.jump(p.end());
|
||||
return expr.map(Node::Expr);
|
||||
}
|
||||
|
||||
@ -123,28 +124,24 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
|
||||
/// Parse a heading.
|
||||
fn heading(p: &mut Parser) -> Node {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user