Syntax functions 🚀

This adds overridable functions that markup desugars into. Specifically:
- \ desugars into linebreak
- Two newlines desugar into parbreak
- * desugars into strong
- _ desugars into emph
- = .. desugars into heading
- `..` desugars into raw
This commit is contained in:
Laurenz 2021-03-21 17:46:09 +01:00
parent 898728f260
commit 5e08028fb3
39 changed files with 654 additions and 405 deletions

View File

@ -28,7 +28,7 @@ fn benchmarks(c: &mut Criterion) {
resources: ResourceLoader::new(), resources: ResourceLoader::new(),
}; };
let scope = library::new(); let scope = library::_new();
let state = State::default(); let state = State::default();
// Prepare intermediate results and run warm. // Prepare intermediate results and run warm.

View File

@ -2,7 +2,7 @@ use std::rc::Rc;
use super::{Scope, Scopes, Value}; use super::{Scope, Scopes, Value};
use crate::syntax::visit::{visit_expr, Visit}; use crate::syntax::visit::{visit_expr, Visit};
use crate::syntax::{Expr, Ident}; use crate::syntax::{Expr, Ident, Node};
/// A visitor that captures variable slots. /// A visitor that captures variable slots.
#[derive(Debug)] #[derive(Debug)]
@ -26,20 +26,36 @@ impl<'a> CapturesVisitor<'a> {
pub fn finish(self) -> Scope { pub fn finish(self) -> Scope {
self.captures self.captures
} }
/// Find out whether the name is not locally defined and if so if it can be
/// captured.
fn process(&mut self, name: &str) {
if self.internal.get(name).is_none() {
if let Some(slot) = self.external.get(name) {
self.captures.def_slot(name, Rc::clone(slot));
}
}
}
} }
impl<'ast> Visit<'ast> for CapturesVisitor<'_> { impl<'ast> Visit<'ast> for CapturesVisitor<'_> {
fn visit_node(&mut self, node: &'ast Node) {
match node {
Node::Text(_) => {}
Node::Space => {}
Node::Linebreak(_) => self.process(Node::LINEBREAK),
Node::Parbreak(_) => self.process(Node::PARBREAK),
Node::Strong(_) => self.process(Node::STRONG),
Node::Emph(_) => self.process(Node::EMPH),
Node::Heading(_) => self.process(Node::HEADING),
Node::Raw(_) => self.process(Node::RAW),
Node::Expr(expr) => self.visit_expr(expr),
}
}
fn visit_expr(&mut self, node: &'ast Expr) { fn visit_expr(&mut self, node: &'ast Expr) {
match node { match node {
Expr::Ident(ident) => { Expr::Ident(ident) => self.process(ident),
// Find out whether the identifier is not locally defined, but
// captured, and if so, capture its value.
if self.internal.get(ident).is_none() {
if let Some(slot) = self.external.get(ident) {
self.captures.def_slot(ident.as_str(), Rc::clone(slot));
}
}
}
expr => visit_expr(self, expr), expr => visit_expr(self, expr),
} }
} }

View File

@ -20,23 +20,23 @@ use crate::geom::{Angle, Length, Relative};
use crate::syntax::visit::Visit; use crate::syntax::visit::Visit;
use crate::syntax::*; use crate::syntax::*;
/// Evaluate all expressions in a syntax tree. /// Evaluate all nodes in a syntax tree.
/// ///
/// The `scope` consists of the base definitions that are present from the /// The `scope` consists of the base definitions that are present from the
/// beginning (typically, the standard library). /// beginning (typically, the standard library).
pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass<ExprMap> { pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass<NodeMap> {
let mut ctx = EvalContext::new(env, scope); let mut ctx = EvalContext::new(env, scope);
let map = tree.eval(&mut ctx); let map = tree.eval(&mut ctx);
Pass::new(map, ctx.diags) Pass::new(map, ctx.diags)
} }
/// A map from expressions to the values they evaluated to. /// A map from nodes to the values they evaluated to.
/// ///
/// The raw pointers point into the expressions contained in some [`Tree`]. /// The raw pointers point into the nodes contained in some [`Tree`]. Since the
/// Since the lifetime is erased, the tree could go out of scope while the hash /// lifetime is erased, the tree could go out of scope while the hash map still
/// map still lives. Although this could lead to lookup panics, it is not unsafe /// lives. Although this could lead to lookup panics, it is not unsafe since the
/// since the pointers are never dereferenced. /// pointers are never dereferenced.
pub type ExprMap = HashMap<*const Expr, Value>; pub type NodeMap = HashMap<*const Node, Value>;
/// The context for evaluation. /// The context for evaluation.
#[derive(Debug)] #[derive(Debug)]
@ -75,23 +75,24 @@ pub trait Eval {
} }
impl Eval for Tree { impl Eval for Tree {
type Output = ExprMap; type Output = NodeMap;
fn eval(&self, ctx: &mut EvalContext) -> Self::Output { fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
struct ExprVisitor<'a, 'b> { let mut map = NodeMap::new();
map: ExprMap,
ctx: &'a mut EvalContext<'b>, for node in self {
let value = if let Some(call) = node.desugar() {
call.eval(ctx)
} else if let Node::Expr(expr) = node {
expr.eval(ctx)
} else {
continue;
};
map.insert(node as *const _, value);
} }
impl<'ast> Visit<'ast> for ExprVisitor<'_, '_> { map
fn visit_expr(&mut self, node: &'ast Expr) {
self.map.insert(node as *const _, node.eval(self.ctx));
}
}
let mut visitor = ExprVisitor { map: ExprMap::new(), ctx };
visitor.visit_tree(self);
visitor.map
} }
} }
@ -99,46 +100,36 @@ impl Eval for Expr {
type Output = Value; type Output = Value;
fn eval(&self, ctx: &mut EvalContext) -> Self::Output { fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
match self { match *self {
Self::Lit(lit) => lit.eval(ctx), Self::None(_) => Value::None,
Self::Ident(v) => match ctx.scopes.get(&v) { Self::Bool(_, v) => Value::Bool(v),
Self::Int(_, v) => Value::Int(v),
Self::Float(_, v) => Value::Float(v),
Self::Length(_, v, unit) => Value::Length(Length::with_unit(v, unit)),
Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)),
Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)),
Self::Color(_, v) => Value::Color(Color::Rgba(v)),
Self::Str(_, ref v) => Value::Str(v.clone()),
Self::Ident(ref v) => match ctx.scopes.get(&v) {
Some(slot) => slot.borrow().clone(), Some(slot) => slot.borrow().clone(),
None => { None => {
ctx.diag(error!(v.span, "unknown variable")); ctx.diag(error!(v.span, "unknown variable"));
Value::Error Value::Error
} }
}, },
Self::Array(v) => Value::Array(v.eval(ctx)), Self::Array(ref v) => Value::Array(v.eval(ctx)),
Self::Dict(v) => Value::Dict(v.eval(ctx)), Self::Dict(ref v) => Value::Dict(v.eval(ctx)),
Self::Template(v) => Value::Template(vec![v.eval(ctx)]), Self::Template(ref v) => Value::Template(vec![v.eval(ctx)]),
Self::Group(v) => v.eval(ctx), Self::Group(ref v) => v.eval(ctx),
Self::Block(v) => v.eval(ctx), Self::Block(ref v) => v.eval(ctx),
Self::Call(v) => v.eval(ctx), Self::Call(ref v) => v.eval(ctx),
Self::Closure(v) => v.eval(ctx), Self::Closure(ref v) => v.eval(ctx),
Self::Unary(v) => v.eval(ctx), Self::Unary(ref v) => v.eval(ctx),
Self::Binary(v) => v.eval(ctx), Self::Binary(ref v) => v.eval(ctx),
Self::Let(v) => v.eval(ctx), Self::Let(ref v) => v.eval(ctx),
Self::If(v) => v.eval(ctx), Self::If(ref v) => v.eval(ctx),
Self::While(v) => v.eval(ctx), Self::While(ref v) => v.eval(ctx),
Self::For(v) => v.eval(ctx), Self::For(ref v) => v.eval(ctx),
}
}
}
impl Eval for Lit {
type Output = Value;
fn eval(&self, _: &mut EvalContext) -> Self::Output {
match self.kind {
LitKind::None => Value::None,
LitKind::Bool(v) => Value::Bool(v),
LitKind::Int(v) => Value::Int(v),
LitKind::Float(v) => Value::Float(v),
LitKind::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
LitKind::Color(v) => Value::Color(Color::Rgba(v)),
LitKind::Str(ref v) => Value::Str(v.clone()),
} }
} }
} }

View File

@ -1,5 +1,4 @@
use super::{ArrayValue, DictValue, TemplateNode, Value}; use super::{ArrayValue, DictValue, TemplateNode, Value};
use crate::syntax::Span;
use Value::*; use Value::*;
/// Apply the plus operator to a value. /// Apply the plus operator to a value.
@ -184,7 +183,6 @@ fn value_eq(lhs: &Value, rhs: &Value) -> bool {
(&Linear(a), &Relative(b)) => a.rel == b && a.abs.is_zero(), (&Linear(a), &Relative(b)) => a.rel == b && a.abs.is_zero(),
(Array(a), Array(b)) => array_eq(a, b), (Array(a), Array(b)) => array_eq(a, b),
(Dict(a), Dict(b)) => dict_eq(a, b), (Dict(a), Dict(b)) => dict_eq(a, b),
(Template(a), Template(b)) => Span::without_cmp(|| a == b),
(a, b) => a == b, (a, b) => a == b,
} }
} }

View File

@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use super::{EvalContext, ExprMap}; use super::{EvalContext, NodeMap};
use crate::color::Color; use crate::color::Color;
use crate::diag::DiagSet; use crate::diag::DiagSet;
use crate::exec::ExecContext; use crate::exec::ExecContext;
@ -107,7 +107,7 @@ pub type TemplateValue = Vec<TemplateNode>;
/// ///
/// Evaluating a template expression creates only a single node. Adding multiple /// Evaluating a template expression creates only a single node. Adding multiple
/// templates can yield multi-node templates. /// templates can yield multi-node templates.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone)]
pub enum TemplateNode { pub enum TemplateNode {
/// A template that consists of a syntax tree plus already evaluated /// A template that consists of a syntax tree plus already evaluated
/// expression. /// expression.
@ -115,7 +115,7 @@ pub enum TemplateNode {
/// The syntax tree of the corresponding template expression. /// The syntax tree of the corresponding template expression.
tree: Rc<Tree>, tree: Rc<Tree>,
/// The evaluated expressions for the `tree`. /// The evaluated expressions for the `tree`.
map: ExprMap, map: NodeMap,
}, },
/// A template that was converted from a string. /// A template that was converted from a string.
Str(String), Str(String),
@ -123,6 +123,13 @@ pub enum TemplateNode {
Func(TemplateFunc), Func(TemplateFunc),
} }
impl PartialEq for TemplateNode {
fn eq(&self, _: &Self) -> bool {
// TODO: Figure out what we want here.
false
}
}
/// A reference-counted dynamic template node that can implement custom /// A reference-counted dynamic template node that can implement custom
/// behaviour. /// behaviour.
#[derive(Clone)] #[derive(Clone)]

View File

@ -11,7 +11,7 @@ use crate::geom::{Dir, Gen, Linear, Sides, Size};
use crate::layout::{ use crate::layout::{
Node, PadNode, PageRun, ParNode, SpacingNode, StackNode, TextNode, Tree, Node, PadNode, PageRun, ParNode, SpacingNode, StackNode, TextNode, Tree,
}; };
use crate::parse::is_newline; use crate::parse::{is_newline, Scanner};
use crate::syntax::{Span, Spanned}; use crate::syntax::{Span, Spanned};
/// The context for execution. /// The context for execution.
@ -99,16 +99,19 @@ impl<'a> ExecContext<'a> {
/// ///
/// The text is split into lines at newlines. /// The text is split into lines at newlines.
pub fn push_text(&mut self, text: &str) { pub fn push_text(&mut self, text: &str) {
let mut newline = false; let mut scanner = Scanner::new(text);
for line in text.split_terminator(is_newline) { let mut line = String::new();
if newline {
self.push_linebreak();
}
let node = self.make_text_node(line.into()); while let Some(c) = scanner.eat_merging_crlf() {
self.push(node); if is_newline(c) {
newline = true; self.push(self.make_text_node(mem::take(&mut line)));
self.push_linebreak();
} else {
line.push(c);
}
} }
self.push(self.make_text_node(line));
} }
/// Apply a forced line break. /// Apply a forced line break.

View File

@ -10,24 +10,24 @@ use std::rc::Rc;
use crate::diag::Pass; use crate::diag::Pass;
use crate::env::Env; use crate::env::Env;
use crate::eval::{ExprMap, TemplateFunc, TemplateNode, TemplateValue, Value}; use crate::eval::{NodeMap, TemplateFunc, TemplateNode, TemplateValue, Value};
use crate::layout::{self, FixedNode, SpacingNode, StackNode}; use crate::layout;
use crate::pretty::pretty; use crate::pretty::pretty;
use crate::syntax::*; use crate::syntax::*;
/// Execute a syntax tree to produce a layout tree. /// Execute a syntax tree to produce a layout tree.
/// ///
/// The `map` shall be an expression map computed for this tree with /// The `map` shall be a node map computed for this tree with
/// [`eval`](crate::eval::eval). Note that `tree` must be the _exact_ same tree /// [`eval`](crate::eval::eval). Note that `tree` must be the _exact_ same tree
/// as used for evaluation (no cloned version), because the expression map /// as used for evaluation (no cloned version), because the node map depends on
/// depends on the pointers being stable. /// the pointers being stable.
/// ///
/// The `state` is the base state that may be updated over the course of /// The `state` is the base state that may be updated over the course of
/// execution. /// execution.
pub fn exec( pub fn exec(
env: &mut Env, env: &mut Env,
tree: &Tree, tree: &Tree,
map: &ExprMap, map: &NodeMap,
state: State, state: State,
) -> Pass<layout::Tree> { ) -> Pass<layout::Tree> {
let mut ctx = ExecContext::new(env, state); let mut ctx = ExecContext::new(env, state);
@ -47,14 +47,14 @@ pub trait Exec {
fn exec(&self, ctx: &mut ExecContext); fn exec(&self, ctx: &mut ExecContext);
} }
/// Execute a node with an expression map that applies to it. /// Execute a node with a node map that applies to it.
pub trait ExecWithMap { pub trait ExecWithMap {
/// Execute the node. /// Execute the node.
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap); fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap);
} }
impl ExecWithMap for Tree { impl ExecWithMap for Tree {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) {
for node in self { for node in self {
node.exec_with_map(ctx, map); node.exec_with_map(ctx, map);
} }
@ -62,83 +62,15 @@ impl ExecWithMap for Tree {
} }
impl ExecWithMap for Node { impl ExecWithMap for Node {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) {
match self { match self {
Node::Text(text) => ctx.push_text(text), Node::Text(text) => ctx.push_text(text),
Node::Space => ctx.push_space(), Node::Space => ctx.push_space(),
Node::Linebreak => ctx.push_linebreak(), _ => map[&(self as *const _)].exec(ctx),
Node::Parbreak => ctx.push_parbreak(),
Node::Strong => ctx.state.font.strong ^= true,
Node::Emph => ctx.state.font.emph ^= true,
Node::Heading(heading) => heading.exec_with_map(ctx, map),
Node::Raw(raw) => raw.exec(ctx),
Node::Expr(expr) => map[&(expr as *const _)].exec(ctx),
} }
} }
} }
impl ExecWithMap for HeadingNode {
fn exec_with_map(&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_map(ctx, map);
ctx.push_parbreak();
ctx.state = prev;
}
}
impl Exec for RawNode {
fn exec(&self, ctx: &mut ExecContext) {
let prev = Rc::clone(&ctx.state.font.families);
ctx.set_monospace();
let em = ctx.state.font.font_size();
let leading = ctx.state.par.leading.resolve(em);
let mut children = vec![];
let mut newline = false;
for line in &self.lines {
if newline {
children.push(layout::Node::Spacing(SpacingNode {
amount: leading,
softness: 2,
}));
}
children.push(layout::Node::Text(ctx.make_text_node(line.clone())));
newline = true;
}
if self.block {
ctx.push_parbreak();
}
// This is wrapped in a fixed node to make sure the stack fits to its
// content instead of filling the available area.
ctx.push(FixedNode {
width: None,
height: None,
aspect: None,
child: StackNode {
dirs: ctx.state.dirs,
aligns: ctx.state.aligns,
children,
}
.into(),
});
if self.block {
ctx.push_parbreak();
}
ctx.state.font.families = prev;
}
}
impl Exec for Value { impl Exec for Value {
fn exec(&self, ctx: &mut ExecContext) { fn exec(&self, ctx: &mut ExecContext) {
match self { match self {

View File

@ -3,7 +3,7 @@ use super::*;
/// A relative length. /// A relative length.
/// ///
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the /// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
/// corresponding [literal](crate::syntax::LitKind::Percent). /// corresponding [literal](crate::syntax::Expr::Percent).
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] #[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
pub struct Relative(f64); pub struct Relative(f64);

View File

@ -6,8 +6,8 @@
//! tree]. The structures describing the tree can be found in the [syntax] //! tree]. The structures describing the tree can be found in the [syntax]
//! module. //! module.
//! - **Evaluation:** The next step is to [evaluate] the syntax tree. This //! - **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 //! computes the value of each node in document and stores them in a map from
//! from expression-pointers to values. //! node-pointers to values.
//! - **Execution:** Now, we can [execute] the parsed and evaluated "script". //! - **Execution:** Now, we can [execute] the parsed and evaluated "script".
//! This produces a [layout tree], a high-level, fully styled representation //! This produces a [layout tree], a high-level, fully styled representation
//! of the document. The nodes of this tree are self-contained and //! of the document. The nodes of this tree are self-contained and

View File

@ -1,6 +1,6 @@
use super::*; use super::*;
/// `align`: Align content along the layouting axes. /// `align`: Configure the alignment along the layouting axes.
/// ///
/// # Positional parameters /// # Positional parameters
/// - Alignments: variadic, of type `alignment`. /// - Alignments: variadic, of type `alignment`.

View File

@ -3,6 +3,20 @@ use crate::pretty::pretty;
use super::*; use super::*;
/// `type`: Get the name of a value's type.
///
/// # Positional parameters
/// - Any value.
///
/// # Return value
/// The name of the value's type as a string.
pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
match args.require::<Value>(ctx, "value") {
Some(value) => value.type_name().into(),
None => Value::Error,
}
}
/// `repr`: Get the string representation of a value. /// `repr`: Get the string representation of a value.
/// ///
/// # Positional parameters /// # Positional parameters
@ -17,7 +31,7 @@ pub fn repr(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
} }
} }
/// `rgb`: Create an RGB(A) color. /// `rgb`: Construct an RGB(A) color.
/// ///
/// # Positional parameters /// # Positional parameters
/// - Red component: of type `float`, between 0.0 and 1.0. /// - Red component: of type `float`, between 0.0 and 1.0.
@ -49,17 +63,3 @@ pub fn rgb(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
clamp(a, 255), clamp(a, 255),
))) )))
} }
/// `type`: Find out the name of a value's type.
///
/// # Positional parameters
/// - Any value.
///
/// # Return value
/// The name of the value's type as a string.
pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
match args.require::<Value>(ctx, "value") {
Some(value) => value.type_name().into(),
None => Value::Error,
}
}

172
src/library/markup.rs Normal file
View File

@ -0,0 +1,172 @@
use super::*;
use crate::syntax::{HeadingNode, RawNode};
/// `linebreak`: Start a new line.
///
/// # Syntax
/// This function has dedicated syntax:
/// ```typst
/// This line ends here, \
/// And a new one begins.
/// ```
///
/// # Return value
/// A template that inserts a line break.
pub fn linebreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value {
Value::template(Node::LINEBREAK, move |ctx| {
ctx.push_linebreak();
})
}
/// `parbreak`: Start a new paragraph.
///
/// # Return value
/// A template that inserts a paragraph break.
pub fn parbreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value {
Value::template(Node::PARBREAK, move |ctx| {
ctx.push_parbreak();
})
}
/// `strong`: Signify important text by setting it in bold.
///
/// # Syntax
/// This function has dedicated syntax.
/// ```typst
/// This is *important*!
/// ```
///
/// # Positional parameters
/// - Body: optional, of type `template`.
///
/// # Return value
/// A template that flips the strongness of text. The effect is scoped to the
/// body if present.
pub fn strong(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let body = args.find::<TemplateValue>(ctx);
Value::template(Node::STRONG, move |ctx| {
let snapshot = ctx.state.clone();
ctx.state.font.strong ^= true;
if let Some(body) = &body {
body.exec(ctx);
ctx.state = snapshot;
}
})
}
/// `emph`: Emphasize text by setting it in italics.
///
/// # Syntax
/// This function has dedicated syntax.
/// ```typst
/// I would have _never_ thought so!
/// ```
///
/// # Positional parameters
/// - Body: optional, of type `template`.
///
/// # Return value
/// A template that flips whether text is emphasized. The effect is scoped to
/// the body if present.
pub fn emph(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let body = args.find::<TemplateValue>(ctx);
Value::template(Node::EMPH, move |ctx| {
let snapshot = ctx.state.clone();
ctx.state.font.emph ^= true;
if let Some(body) = &body {
body.exec(ctx);
ctx.state = snapshot;
}
})
}
/// `heading`: A section heading.
///
/// # Syntax
/// This function has dedicated syntax.
/// ```typst
/// = Section
/// ...
///
/// == Subsection
/// ...
/// ```
///
/// # Positional parameters
/// - Body, of type `template`.
///
/// # Named parameters
/// - Section depth: `level`, of type `integer` between 1 and 6.
///
/// # Return value
/// A template that sets the body as a section heading, that is, large and in
/// bold.
pub fn heading(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let level = args.get(ctx, HeadingNode::LEVEL).unwrap_or(1);
let body = args
.require::<TemplateValue>(ctx, HeadingNode::BODY)
.unwrap_or_default();
Value::template(Node::HEADING, move |ctx| {
let snapshot = ctx.state.clone();
let upscale = 1.6 - 0.1 * level as f64;
ctx.state.font.scale *= upscale;
ctx.state.font.strong = true;
body.exec(ctx);
ctx.push_parbreak();
ctx.state = snapshot;
})
}
/// `raw`: Raw text.
///
/// # Syntax
/// This function has dedicated syntax:
/// - For inline-level raw text:
/// ```typst
/// `...`
/// ```
/// - For block-level raw text:
/// ````typst
/// ```rust
/// println!("Hello World!");
/// ```
/// ````
///
/// # Positional parameters
/// - Text, of type `string`.
///
/// # Named parameters
/// - Language for syntax highlighting: `lang`, of type `string`.
/// - Whether the item is block level (split in its own paragraph): `block`, of
/// type `boolean`.
///
/// # Return value
/// A template that sets the text raw, that is, in monospace with optional
/// syntax highlighting.
pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let text = args.require::<String>(ctx, RawNode::TEXT).unwrap_or_default();
let _lang = args.get::<String>(ctx, RawNode::LANG);
let block = args.get(ctx, RawNode::BLOCK).unwrap_or(false);
Value::template(Node::RAW, move |ctx| {
let snapshot = ctx.state.clone();
if block {
ctx.push_parbreak();
}
ctx.set_monospace();
ctx.push_text(&text);
if block {
ctx.push_parbreak();
}
ctx.state = snapshot;
})
}

View File

@ -1,12 +1,13 @@
//! The standard library. //! The standard library.
//! //!
//! Call [`new`] to obtain a [`Scope`] containing all standard library //! Call [`_new`] to obtain a [`Scope`] containing all standard library
//! definitions. //! definitions.
mod align; mod align;
mod base; mod base;
mod font; mod font;
mod image; mod image;
mod markup;
mod pad; mod pad;
mod page; mod page;
mod par; mod par;
@ -17,6 +18,7 @@ pub use self::image::*;
pub use align::*; pub use align::*;
pub use base::*; pub use base::*;
pub use font::*; pub use font::*;
pub use markup::*;
pub use pad::*; pub use pad::*;
pub use page::*; pub use page::*;
pub use par::*; pub use par::*;
@ -32,10 +34,10 @@ use crate::eval::{EvalContext, FuncArgs, TemplateValue, Value};
use crate::exec::{Exec, ExecContext}; use crate::exec::{Exec, ExecContext};
use crate::geom::*; use crate::geom::*;
use crate::layout::VerticalFontMetric; use crate::layout::VerticalFontMetric;
use crate::syntax::Spanned; use crate::syntax::{Node, Spanned};
/// Construct a scope containing all standard library definitions. /// Construct a scope containing all standard library definitions.
pub fn new() -> Scope { pub fn _new() -> Scope {
let mut std = Scope::new(); let mut std = Scope::new();
macro_rules! func { macro_rules! func {
@ -50,6 +52,15 @@ pub fn new() -> Scope {
}; };
} }
// Syntax functions.
func!(Node::EMPH, emph);
func!(Node::HEADING, heading);
func!(Node::STRONG, strong);
func!(Node::RAW, raw);
func!(Node::LINEBREAK, linebreak);
func!(Node::PARBREAK, parbreak);
// Library functions.
func!("align", align); func!("align", align);
func!("circle", circle); func!("circle", circle);
func!("ellipse", ellipse); func!("ellipse", ellipse);
@ -59,14 +70,15 @@ pub fn new() -> Scope {
func!("pad", pad); func!("pad", pad);
func!("page", page); func!("page", page);
func!("pagebreak", pagebreak); func!("pagebreak", pagebreak);
func!("paragraph", par); func!("par", par);
func!("square", square);
func!("rect", rect); func!("rect", rect);
func!("repr", repr); func!("repr", repr);
func!("rgb", rgb); func!("rgb", rgb);
func!("square", square);
func!("type", type_); func!("type", type_);
func!("v", v); func!("v", v);
// Constants.
constant!("left", AlignValue::Left); constant!("left", AlignValue::Left);
constant!("center", AlignValue::Center); constant!("center", AlignValue::Center);
constant!("right", AlignValue::Right); constant!("right", AlignValue::Right);

View File

@ -109,7 +109,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// `pagebreak`: Start a new page. /// `pagebreak`: Start a new page.
/// ///
/// # Return value /// # Return value
/// A template that starts a new page. /// A template that inserts a page break.
pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value { pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value {
let span = args.span; let span = args.span;
Value::template("pagebreak", move |ctx| { Value::template("pagebreak", move |ctx| {

View File

@ -1,6 +1,6 @@
use super::*; use super::*;
/// `paragraph`: Configure paragraphs. /// `par`: Configure paragraphs.
/// ///
/// # Positional parameters /// # Positional parameters
/// - Body: optional, of type `template`. /// - Body: optional, of type `template`.
@ -19,7 +19,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let word_spacing = args.get(ctx, "word-spacing"); let word_spacing = args.get(ctx, "word-spacing");
let body = args.find::<TemplateValue>(ctx); let body = args.find::<TemplateValue>(ctx);
Value::template("paragraph", move |ctx| { Value::template("par", move |ctx| {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
if let Some(spacing) = spacing { if let Some(spacing) = spacing {

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
use crate::layout::SpacingNode; use crate::layout::SpacingNode;
/// `h`: Add horizontal spacing. /// `h`: Insert horizontal spacing.
/// ///
/// # Positional parameters /// # Positional parameters
/// - Amount of spacing: of type `linear` relative to current font size. /// - Amount of spacing: of type `linear` relative to current font size.
@ -12,7 +12,7 @@ pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
spacing_impl(ctx, args, SpecAxis::Horizontal) spacing_impl(ctx, args, SpecAxis::Horizontal)
} }
/// `v`: Add vertical spacing. /// `v`: Insert vertical spacing.
/// ///
/// # Positional parameters /// # Positional parameters
/// - Amount of spacing: of type `linear` relative to current font size. /// - Amount of spacing: of type `linear` relative to current font size.

View File

@ -44,7 +44,7 @@ fn main() -> anyhow::Result<()> {
resources: ResourceLoader::new(), resources: ResourceLoader::new(),
}; };
let scope = library::new(); let scope = library::_new();
let state = State::default(); let state = State::default();
let Pass { output: frames, diags } = typeset(&mut env, &src, &scope, state); let Pass { output: frames, diags } = typeset(&mut env, &src, &scope, state);

View File

@ -31,7 +31,7 @@ fn tree(p: &mut Parser) -> Tree {
let mut tree = vec![]; let mut tree = vec![];
while !p.eof() { while !p.eof() {
if let Some(node) = node(p, &mut at_start) { if let Some(node) = node(p, &mut at_start) {
if !matches!(node, Node::Parbreak | Node::Space) { if !matches!(node, Node::Parbreak(_) | Node::Space) {
at_start = false; at_start = false;
} }
tree.push(node); tree.push(node);
@ -43,19 +43,24 @@ fn tree(p: &mut Parser) -> Tree {
/// Parse a syntax node. /// Parse a syntax node.
fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
let token = p.peek()?; let token = p.peek()?;
let span = p.peek_span();
let node = match token { let node = match token {
// Whitespace. // Whitespace.
Token::Space(newlines) => { Token::Space(newlines) => {
*at_start |= newlines > 0; *at_start |= newlines > 0;
if newlines < 2 { Node::Space } else { Node::Parbreak } if newlines < 2 {
Node::Space
} else {
Node::Parbreak(span)
}
} }
// Text. // Text.
Token::Text(text) => Node::Text(text.into()), Token::Text(text) => Node::Text(text.into()),
// Markup. // Markup.
Token::Star => Node::Strong, Token::Star => Node::Strong(span),
Token::Underscore => Node::Emph, Token::Underscore => Node::Emph(span),
Token::Eq => { Token::Eq => {
if *at_start { if *at_start {
return Some(heading(p)); return Some(heading(p));
@ -64,7 +69,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
} }
} }
Token::Tilde => Node::Text("\u{00A0}".into()), Token::Tilde => Node::Text("\u{00A0}".into()),
Token::Backslash => Node::Linebreak, Token::Backslash => Node::Linebreak(span),
Token::Raw(t) => raw(p, t), Token::Raw(t) => raw(p, t),
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
@ -120,28 +125,33 @@ fn heading(p: &mut Parser) -> Node {
p.assert(Token::Eq); p.assert(Token::Eq);
// Count depth. // Count depth.
let mut level: usize = 0; let mut level: usize = 1;
while p.eat_if(Token::Eq) { while p.eat_if(Token::Eq) {
level += 1; level += 1;
} }
if level > 5 { if level > 6 {
p.diag(warning!(start .. p.end(), "should not exceed depth 6")); p.diag(warning!(start .. p.end(), "should not exceed depth 6"));
level = 5; level = 6;
} }
// Parse the heading contents. // Parse the heading contents.
let mut contents = vec![]; let mut tree = vec![];
while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) { while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) {
contents.extend(node(p, &mut false)); tree.extend(node(p, &mut false));
} }
Node::Heading(HeadingNode { level, contents }) Node::Heading(HeadingNode {
span: p.span(start),
level,
contents: Rc::new(tree),
})
} }
/// Handle a raw block. /// Handle a raw block.
fn raw(p: &mut Parser, token: RawToken) -> Node { fn raw(p: &mut Parser, token: RawToken) -> Node {
let raw = resolve::resolve_raw(token.text, token.backticks, p.start()); let span = p.peek_span();
let raw = resolve::resolve_raw(span, token.text, token.backticks);
if !token.terminated { if !token.terminated {
p.diag(error!(p.peek_span().end, "expected backtick(s)")); p.diag(error!(p.peek_span().end, "expected backtick(s)"));
} }
@ -280,17 +290,18 @@ fn primary(p: &mut Parser, atomic: bool) -> Option<Expr> {
/// Parse a literal. /// Parse a literal.
fn literal(p: &mut Parser) -> Option<Expr> { fn literal(p: &mut Parser) -> Option<Expr> {
let kind = match p.peek()? { let span = p.peek_span();
let expr = match p.peek()? {
// Basic values. // Basic values.
Token::None => LitKind::None, Token::None => Expr::None(span),
Token::Bool(b) => LitKind::Bool(b), Token::Bool(b) => Expr::Bool(span, b),
Token::Int(i) => LitKind::Int(i), Token::Int(i) => Expr::Int(span, i),
Token::Float(f) => LitKind::Float(f), Token::Float(f) => Expr::Float(span, f),
Token::Length(val, unit) => LitKind::Length(val, unit), Token::Length(val, unit) => Expr::Length(span, val, unit),
Token::Angle(val, unit) => LitKind::Angle(val, unit), Token::Angle(val, unit) => Expr::Angle(span, val, unit),
Token::Percent(p) => LitKind::Percent(p), Token::Percent(p) => Expr::Percent(span, p),
Token::Color(color) => LitKind::Color(color), Token::Color(color) => Expr::Color(span, color),
Token::Str(token) => LitKind::Str({ Token::Str(token) => Expr::Str(span, {
if !token.terminated { if !token.terminated {
p.expected_at("quote", p.peek_span().end); p.expected_at("quote", p.peek_span().end);
} }
@ -298,7 +309,8 @@ fn literal(p: &mut Parser) -> Option<Expr> {
}), }),
_ => return None, _ => return None,
}; };
Some(Expr::Lit(Lit { span: p.eat_span(), kind })) p.eat();
Some(expr)
} }
/// Parse something that starts with a parenthesis, which can be either of: /// Parse something that starts with a parenthesis, which can be either of:

View File

@ -1,5 +1,5 @@
use super::{is_newline, Scanner}; use super::{is_newline, Scanner};
use crate::syntax::{Ident, Pos, RawNode}; use crate::syntax::{Ident, RawNode, Span};
/// Resolve all escape sequences in a string. /// Resolve all escape sequences in a string.
pub fn resolve_string(string: &str) -> String { pub fn resolve_string(string: &str) -> String {
@ -47,19 +47,17 @@ pub fn resolve_hex(sequence: &str) -> Option<char> {
} }
/// Resolve the language tag and trims the raw text. /// Resolve the language tag and trims the raw text.
pub fn resolve_raw(text: &str, backticks: usize, start: Pos) -> RawNode { pub fn resolve_raw(span: Span, text: &str, backticks: usize) -> RawNode {
if backticks > 1 { if backticks > 1 {
let (tag, inner) = split_at_lang_tag(text); let (tag, inner) = split_at_lang_tag(text);
let (lines, had_newline) = trim_and_split_raw(inner); let (text, block) = trim_and_split_raw(inner);
RawNode { let lang = Ident::new(tag, span.start .. span.start + tag.len());
lang: Ident::new(tag, start .. start + tag.len()), RawNode { span, lang, text, block }
lines,
block: had_newline,
}
} else { } else {
RawNode { RawNode {
span,
lang: None, lang: None,
lines: split_lines(text), text: split_lines(text).join("\n"),
block: false, block: false,
} }
} }
@ -77,7 +75,7 @@ fn split_at_lang_tag(raw: &str) -> (&str, &str) {
/// Trim raw text and splits it into lines. /// Trim raw text and splits it into lines.
/// ///
/// Returns whether at least one newline was contained in `raw`. /// Returns whether at least one newline was contained in `raw`.
fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) { fn trim_and_split_raw(mut raw: &str) -> (String, bool) {
// Trims one space at the start. // Trims one space at the start.
raw = raw.strip_prefix(' ').unwrap_or(raw); raw = raw.strip_prefix(' ').unwrap_or(raw);
@ -87,8 +85,8 @@ fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) {
} }
let mut lines = split_lines(raw); let mut lines = split_lines(raw);
let had_newline = lines.len() > 1;
let is_whitespace = |line: &String| line.chars().all(char::is_whitespace); let is_whitespace = |line: &String| line.chars().all(char::is_whitespace);
let had_newline = lines.len() > 1;
// Trims a sequence of whitespace followed by a newline at the start. // Trims a sequence of whitespace followed by a newline at the start.
if lines.first().map_or(false, is_whitespace) { if lines.first().map_or(false, is_whitespace) {
@ -100,7 +98,7 @@ fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) {
lines.pop(); lines.pop();
} }
(lines, had_newline) (lines.join("\n"), had_newline)
} }
/// Split a string into a vector of lines /// Split a string into a vector of lines
@ -171,64 +169,53 @@ mod tests {
raw: &str, raw: &str,
backticks: usize, backticks: usize,
lang: Option<&str>, lang: Option<&str>,
lines: &[&str], text: &str,
block: bool, block: bool,
) { ) {
Span::without_cmp(|| assert_eq!(resolve_raw(raw, backticks, Pos(0)), RawNode { Span::without_cmp(|| {
lang: lang.and_then(|id| Ident::new(id, 0)), assert_eq!(resolve_raw(Span::ZERO, raw, backticks), RawNode {
lines: lines.iter().map(ToString::to_string).collect(), span: Span::ZERO,
block, lang: lang.and_then(|id| Ident::new(id, 0)),
})); text: text.into(),
block,
});
});
} }
// Just one backtick. // Just one backtick.
test("py", 1, None, &["py"], false); test("py", 1, None, "py", false);
test("1\n2", 1, None, &["1", "2"], false); test("1\n2", 1, None, "1\n2", false);
test("1\r\n2", 1, None, &["1", "2"], false); test("1\r\n2", 1, None, "1\n2", false);
// More than one backtick with lang tag. // More than one backtick with lang tag.
test("js alert()", 2, Some("js"), &["alert()"], false); test("js alert()", 2, Some("js"), "alert()", false);
test("py quit(\n\n)", 3, Some("py"), &["quit(", "", ")"], true); test("py quit(\n\n)", 3, Some("py"), "quit(\n\n)", true);
test("", 2, None, &[], false); test("", 2, None, "", false);
// Trimming of whitespace (tested more thoroughly in separate test). // Trimming of whitespace (tested more thoroughly in separate test).
test(" a", 2, None, &["a"], false); test(" a", 2, None, "a", false);
test(" a", 2, None, &[" a"], false); test(" a", 2, None, " a", false);
test(" \na", 2, None, &["a"], true); test(" \na", 2, None, "a", true);
} }
#[test] #[test]
fn test_trim_raw() { fn test_trim_raw() {
#[track_caller] #[track_caller]
fn test(text: &str, expected: Vec<&str>) { fn test(text: &str, expected: &str) {
assert_eq!(trim_and_split_raw(text).0, expected); assert_eq!(trim_and_split_raw(text).0, expected);
} }
test(" hi", vec!["hi"]); test(" hi", "hi");
test(" hi", vec![" hi"]); test(" hi", " hi");
test("\nhi", vec!["hi"]); test("\nhi", "hi");
test(" \n hi", vec![" hi"]); test(" \n hi", " hi");
test("hi` ", vec!["hi`"]); test("hi` ", "hi`");
test("hi` ", vec!["hi` "]); test("hi` ", "hi` ");
test("hi` ", vec!["hi` "]); test("hi` ", "hi` ");
test("hi ", vec!["hi "]); test("hi ", "hi ");
test("hi ", vec!["hi "]); test("hi ", "hi ");
test("hi\n", vec!["hi"]); test("hi\n", "hi");
test("hi \n ", vec!["hi "]); test("hi \n ", "hi ");
test(" \n hi \n ", vec![" hi "]); test(" \n hi \n ", " hi ");
}
#[test]
fn test_split_lines() {
#[track_caller]
fn test(text: &str, expected: Vec<&str>) {
assert_eq!(split_lines(text), expected);
}
test("raw\ntext", vec!["raw", "text"]);
test("a\r\nb", vec!["a", "b"]);
test("a\n\nb", vec!["a", "", "b"]);
test("a\r\x0Bb", vec!["a", "", "b"]);
test("a\r\n\r\nb", vec!["a", "", "b"]);
} }
} }

View File

@ -17,8 +17,8 @@ where
p.finish() p.finish()
} }
/// Pretty print an item with an expression map and return the resulting string. /// Pretty print an item with a node map and return the resulting string.
pub fn pretty_with_map<T>(item: &T, map: &ExprMap) -> String pub fn pretty_with_map<T>(item: &T, map: &NodeMap) -> String
where where
T: PrettyWithMap + ?Sized, T: PrettyWithMap + ?Sized,
{ {
@ -33,10 +33,10 @@ pub trait Pretty {
fn pretty(&self, p: &mut Printer); fn pretty(&self, p: &mut Printer);
} }
/// Pretty print an item with an expression map that applies to it. /// Pretty print an item with a node map that applies to it.
pub trait PrettyWithMap { pub trait PrettyWithMap {
/// Pretty print this item into the given printer. /// Pretty print this item into the given printer.
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>); fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>);
} }
impl<T> Pretty for T impl<T> Pretty for T
@ -104,7 +104,7 @@ impl Write for Printer {
} }
impl PrettyWithMap for Tree { impl PrettyWithMap for Tree {
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) { fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
for node in self { for node in self {
node.pretty_with_map(p, map); node.pretty_with_map(p, map);
} }
@ -112,20 +112,20 @@ impl PrettyWithMap for Tree {
} }
impl PrettyWithMap for Node { impl PrettyWithMap for Node {
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) { fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
match self { match self {
Self::Strong => p.push('*'),
Self::Emph => p.push('_'),
Self::Space => p.push(' '),
Self::Linebreak => p.push_str(r"\"),
Self::Parbreak => p.push_str("\n\n"),
// TODO: Handle escaping. // TODO: Handle escaping.
Self::Text(text) => p.push_str(text), Self::Text(text) => p.push_str(text),
Self::Space => p.push(' '),
Self::Strong(_) => p.push('*'),
Self::Emph(_) => p.push('_'),
Self::Linebreak(_) => p.push_str(r"\"),
Self::Parbreak(_) => p.push_str("\n\n"),
Self::Heading(heading) => heading.pretty_with_map(p, map), Self::Heading(heading) => heading.pretty_with_map(p, map),
Self::Raw(raw) => raw.pretty(p), Self::Raw(raw) => raw.pretty(p),
Self::Expr(expr) => { Self::Expr(expr) => {
if let Some(map) = map { if let Some(map) = map {
let value = &map[&(expr as *const _)]; let value = &map[&(self as *const _)];
value.pretty(p); value.pretty(p);
} else { } else {
if expr.has_short_form() { if expr.has_short_form() {
@ -139,8 +139,8 @@ impl PrettyWithMap for Node {
} }
impl PrettyWithMap for HeadingNode { impl PrettyWithMap for HeadingNode {
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) { fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
for _ in 0 ..= self.level { for _ in 0 .. self.level {
p.push('='); p.push('=');
} }
self.contents.pretty_with_map(p, map); self.contents.pretty_with_map(p, map);
@ -158,17 +158,14 @@ impl Pretty for RawNode {
} }
// More backticks may be required if there are lots of consecutive // More backticks may be required if there are lots of consecutive
// backticks in the lines. // backticks.
let mut count; let mut count = 0;
for line in &self.lines { for c in self.text.chars() {
count = 0; if c == '`' {
for c in line.chars() { count += 1;
if c == '`' { backticks = backticks.max(3).max(count + 1);
count += 1; } else {
backticks = backticks.max(3).max(count + 1); count = 0;
} else {
count = 0;
}
} }
} }
@ -190,12 +187,12 @@ impl Pretty for RawNode {
} }
// The lines. // The lines.
p.join(&self.lines, "\n", |line, p| p.push_str(line)); p.push_str(&self.text);
// End untrimming. // End untrimming.
if self.block { if self.block {
p.push('\n'); p.push('\n');
} else if self.lines.last().map_or(false, |line| line.trim_end().ends_with('`')) { } else if self.text.trim_end().ends_with('`') {
p.push(' '); p.push(' ');
} }
@ -209,7 +206,15 @@ impl Pretty for RawNode {
impl Pretty for Expr { impl Pretty for Expr {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
match self { match self {
Self::Lit(v) => v.pretty(p), Self::None(_) => p.push_str("none"),
Self::Bool(_, v) => v.pretty(p),
Self::Int(_, v) => v.pretty(p),
Self::Float(_, v) => v.pretty(p),
Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Percent(_, v) => write!(p, "{}%", v).unwrap(),
Self::Color(_, v) => v.pretty(p),
Self::Str(_, v) => v.pretty(p),
Self::Ident(v) => v.pretty(p), Self::Ident(v) => v.pretty(p),
Self::Array(v) => v.pretty(p), Self::Array(v) => v.pretty(p),
Self::Dict(v) => v.pretty(p), Self::Dict(v) => v.pretty(p),
@ -228,28 +233,6 @@ impl Pretty for Expr {
} }
} }
impl Pretty for Lit {
fn pretty(&self, p: &mut Printer) {
self.kind.pretty(p);
}
}
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),
Self::Length(v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Angle(v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Percent(v) => write!(p, "{}%", v).unwrap(),
Self::Color(v) => v.pretty(p),
Self::Str(v) => v.pretty(p),
}
}
}
impl Pretty for ArrayExpr { impl Pretty for ArrayExpr {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
p.push('('); p.push('(');
@ -784,7 +767,7 @@ mod tests {
test_value( test_value(
vec![ vec![
TemplateNode::Tree { TemplateNode::Tree {
tree: Rc::new(vec![Node::Strong]), tree: Rc::new(vec![Node::Strong(Span::ZERO)]),
map: HashMap::new(), map: HashMap::new(),
}, },
TemplateNode::Func(TemplateFunc::new("example", |_| {})), TemplateNode::Func(TemplateFunc::new("example", |_| {})),

View File

@ -7,8 +7,27 @@ use crate::geom::{AngularUnit, LengthUnit};
/// An expression. /// An expression.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Expr { pub enum Expr {
/// A literal, like `11pt` or `"hi"`. /// The none literal: `none`.
Lit(Lit), None(Span),
/// A boolean literal: `true`, `false`.
Bool(Span, bool),
/// An integer literal: `120`.
Int(Span, i64),
/// A floating-point literal: `1.2`, `10e-4`.
Float(Span, f64),
/// A length literal: `12pt`, `3cm`.
Length(Span, f64, LengthUnit),
/// An angle literal: `1.5rad`, `90deg`.
Angle(Span, 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(Span, f64),
/// A color literal: `#ffccee`.
Color(Span, RgbaColor),
/// A string literal: `"hello!"`.
Str(Span, String),
/// An identifier: `left`. /// An identifier: `left`.
Ident(Ident), Ident(Ident),
/// An array expression: `(1, "hi", 12cm)`. /// An array expression: `(1, "hi", 12cm)`.
@ -42,22 +61,30 @@ pub enum Expr {
impl Expr { impl Expr {
/// The source code location. /// The source code location.
pub fn span(&self) -> Span { pub fn span(&self) -> Span {
match self { match *self {
Self::Lit(v) => v.span, Self::None(span) => span,
Self::Ident(v) => v.span, Self::Bool(span, _) => span,
Self::Array(v) => v.span, Self::Int(span, _) => span,
Self::Dict(v) => v.span, Self::Float(span, _) => span,
Self::Template(v) => v.span, Self::Length(span, _, _) => span,
Self::Group(v) => v.span, Self::Angle(span, _, _) => span,
Self::Block(v) => v.span, Self::Percent(span, _) => span,
Self::Unary(v) => v.span, Self::Color(span, _) => span,
Self::Binary(v) => v.span, Self::Str(span, _) => span,
Self::Call(v) => v.span, Self::Ident(ref v) => v.span,
Self::Closure(v) => v.span, Self::Array(ref v) => v.span,
Self::Let(v) => v.span, Self::Dict(ref v) => v.span,
Self::If(v) => v.span, Self::Template(ref v) => v.span,
Self::While(v) => v.span, Self::Group(ref v) => v.span,
Self::For(v) => v.span, Self::Block(ref v) => v.span,
Self::Unary(ref v) => v.span,
Self::Binary(ref v) => v.span,
Self::Call(ref v) => v.span,
Self::Closure(ref v) => v.span,
Self::Let(ref v) => v.span,
Self::If(ref v) => v.span,
Self::While(ref v) => v.span,
Self::For(ref v) => v.span,
} }
} }
@ -74,41 +101,6 @@ impl Expr {
} }
} }
/// A literal, like `11pt` or `"hi"`.
#[derive(Debug, Clone, PartialEq)]
pub struct Lit {
/// The source code location.
pub span: Span,
/// The kind of literal.
pub kind: LitKind,
}
/// 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),
}
/// An array expression: `(1, "hi", 12cm)`. /// An array expression: `(1, "hi", 12cm)`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ArrayExpr { pub struct ArrayExpr {

View File

@ -1,35 +1,84 @@
use std::rc::Rc;
use super::*; use super::*;
/// A syntax node, encompassing a single logical entity of parsed source code. /// A syntax node, encompassing a single logical entity of parsed source code.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Node { pub enum Node {
/// Strong text was enabled / disabled.
Strong,
/// Emphasized text was enabled / disabled.
Emph,
/// Whitespace containing less than two newlines.
Space,
/// A forced line break.
Linebreak,
/// A paragraph break.
Parbreak,
/// Plain text. /// Plain text.
Text(String), Text(String),
/// A section heading. /// Whitespace containing less than two newlines.
Space,
/// A forced line break: `\`.
Linebreak(Span),
/// A paragraph break: Two or more newlines.
Parbreak(Span),
/// Strong text was enabled / disabled: `*`.
Strong(Span),
/// Emphasized text was enabled / disabled: `_`.
Emph(Span),
/// A section heading: `= Introduction`.
Heading(HeadingNode), Heading(HeadingNode),
/// An optionally syntax-highlighted raw block. /// A raw block with optional syntax highlighting: `` `...` ``.
Raw(RawNode), Raw(RawNode),
/// An expression. /// An expression.
Expr(Expr), Expr(Expr),
} }
impl Node {
// The names of the corresponding library functions.
pub const LINEBREAK: &'static str = "linebreak";
pub const PARBREAK: &'static str = "parbreak";
pub const STRONG: &'static str = "strong";
pub const EMPH: &'static str = "emph";
pub const HEADING: &'static str = "heading";
pub const RAW: &'static str = "raw";
/// Desugar markup into a function call.
pub fn desugar(&self) -> Option<CallExpr> {
match *self {
Node::Text(_) => None,
Node::Space => None,
Node::Linebreak(span) => Some(call(span, Self::LINEBREAK)),
Node::Parbreak(span) => Some(call(span, Self::PARBREAK)),
Node::Strong(span) => Some(call(span, Self::STRONG)),
Node::Emph(span) => Some(call(span, Self::EMPH)),
Self::Heading(ref heading) => Some(heading.desugar()),
Self::Raw(ref raw) => Some(raw.desugar()),
Node::Expr(_) => None,
}
}
}
/// A section heading: `= Introduction`. /// A section heading: `= Introduction`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct HeadingNode { pub struct HeadingNode {
/// The section depth (numer of equals signs minus 1). /// The source code location.
pub span: Span,
/// The section depth (numer of equals signs).
pub level: usize, pub level: usize,
/// The contents of the heading. /// The contents of the heading.
pub contents: Tree, pub contents: Rc<Tree>,
}
impl HeadingNode {
pub const LEVEL: &'static str = "level";
pub const BODY: &'static str = "body";
/// Desugar into a function call.
pub fn desugar(&self) -> CallExpr {
let Self { span, level, ref contents } = *self;
let mut call = call(span, Node::HEADING);
call.args.items.push(CallArg::Named(Named {
name: ident(span, Self::LEVEL),
expr: Expr::Int(span, level as i64),
}));
call.args.items.push(CallArg::Pos(Expr::Template(TemplateExpr {
span,
tree: Rc::clone(&contents),
})));
call
}
} }
/// A raw block with optional syntax highlighting: `` `...` ``. /// A raw block with optional syntax highlighting: `` `...` ``.
@ -97,12 +146,50 @@ pub struct HeadingNode {
/// whitespace simply by adding more spaces. /// whitespace simply by adding more spaces.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct RawNode { pub struct RawNode {
/// The source code location.
pub span: Span,
/// An optional identifier specifying the language to syntax-highlight in. /// An optional identifier specifying the language to syntax-highlight in.
pub lang: Option<Ident>, pub lang: Option<Ident>,
/// The lines of raw text, determined as the raw string between the /// The raw text, determined as the raw string between the backticks trimmed
/// backticks trimmed according to the above rules and split at newlines. /// according to the above rules.
pub lines: Vec<String>, pub text: String,
/// Whether the element is block-level, that is, it has 3+ backticks /// Whether the element is block-level, that is, it has 3+ backticks
/// and contains at least one newline. /// and contains at least one newline.
pub block: bool, pub block: bool,
} }
impl RawNode {
pub const LANG: &'static str = "lang";
pub const BLOCK: &'static str = "block";
pub const TEXT: &'static str = "text";
/// Desugar into a function call.
pub fn desugar(&self) -> CallExpr {
let Self { span, ref lang, ref text, block } = *self;
let mut call = call(span, Node::RAW);
if let Some(lang) = lang {
call.args.items.push(CallArg::Named(Named {
name: ident(span, Self::LANG),
expr: Expr::Str(span, lang.string.clone()),
}));
}
call.args.items.push(CallArg::Named(Named {
name: ident(span, Self::BLOCK),
expr: Expr::Bool(span, block),
}));
call.args.items.push(CallArg::Pos(Expr::Str(span, text.clone())));
call
}
}
fn call(span: Span, name: &str) -> CallExpr {
CallExpr {
span,
callee: Box::new(Expr::Ident(Ident { span, string: name.into() })),
args: CallArgs { span, items: vec![] },
}
}
fn ident(span: Span, string: &str) -> Ident {
Ident { span, string: string.into() }
}

View File

@ -121,7 +121,7 @@ pub enum Token<'s> {
/// A percentage: `50%`. /// A percentage: `50%`.
/// ///
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding /// _Note_: `50%` is stored as `50.0` here, as in the corresponding
/// [literal](super::LitKind::Percent). /// [literal](super::Expr::Percent).
Percent(f64), Percent(f64),
/// A color value: `#20d82a`. /// A color value: `#20d82a`.
Color(RgbaColor), Color(RgbaColor),

View File

@ -50,13 +50,13 @@ visit! {
fn visit_node(v, node: &Node) { fn visit_node(v, node: &Node) {
match node { match node {
Node::Strong => {}
Node::Emph => {}
Node::Space => {}
Node::Linebreak => {}
Node::Parbreak => {}
Node::Text(_) => {} Node::Text(_) => {}
Node::Heading(n) => v.visit_tree(&n.contents), Node::Space => {}
Node::Strong(_) => {}
Node::Linebreak(_) => {}
Node::Parbreak(_) => {}
Node::Emph(_) => {}
Node::Heading(heading) => v.visit_tree(&heading.contents),
Node::Raw(_) => {} Node::Raw(_) => {}
Node::Expr(expr) => v.visit_expr(expr), Node::Expr(expr) => v.visit_expr(expr),
} }
@ -64,7 +64,15 @@ visit! {
fn visit_expr(v, node: &Expr) { fn visit_expr(v, node: &Expr) {
match node { match node {
Expr::Lit(_) => {} Expr::None(_) => {}
Expr::Bool(_, _) => {}
Expr::Int(_, _) => {}
Expr::Float(_, _) => {}
Expr::Length(_, _, _) => {}
Expr::Angle(_, _, _) => {}
Expr::Percent(_, _) => {}
Expr::Color(_, _) => {}
Expr::Str(_, _) => {}
Expr::Ident(_) => {} Expr::Ident(_) => {}
Expr::Array(e) => v.visit_array(e), Expr::Array(e) => v.visit_array(e),
Expr::Dict(e) => v.visit_dict(e), Expr::Dict(e) => v.visit_dict(e),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -122,10 +122,7 @@
#test((1, 2, 3) == (1, 2.0) + (3,), true) #test((1, 2, 3) == (1, 2.0) + (3,), true)
#test((:) == (a: 1), false) #test((:) == (a: 1), false)
#test((a: 2 - 1.0, b: 2) == (b: 2, a: 1), true) #test((a: 2 - 1.0, b: 2) == (b: 2, a: 1), true)
#test([*Hi*] == [*Hi*], true)
#test("a" != "a", false) #test("a" != "a", false)
#test([*] != [_], true)
--- ---
// Test comparison operators. // Test comparison operators.

View File

@ -3,11 +3,11 @@
--- ---
// Test configuring paragraph properties. // Test configuring paragraph properties.
#paragraph(spacing: 10pt, leading: 25%, word-spacing: 1pt) #par(spacing: 10pt, leading: 25%, word-spacing: 1pt)
But, soft! what light through yonder window breaks? It is the east, and Juliet But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. is the sun.
--- ---
// Test that it finishes an existing paragraph. // Test that it finishes an existing paragraph.
Hello #paragraph(word-spacing: 0pt) t h e r e ! Hello #par(word-spacing: 0pt) t h e r e !

View File

@ -10,5 +10,14 @@ Partly em_phas_ized.
// Scoped to body. // Scoped to body.
#rect[_Scoped] to body. #rect[_Scoped] to body.
// Unterminated is fine. ---
_The End #let emph = strong
_Strong_
#let emph() = "Hi"
_, _!
#let emph = "hi"
// Error: 1-2 expected function, found string
_

View File

@ -42,3 +42,11 @@ No = heading
// Escaped. // Escaped.
\= No heading \= No heading
---
// Make small, but double heading.
#let heading(contents) = heading(contents + contents, level: 6)
// The new heading's argument list doesn't contain `level`.
// Error: 1-11 unexpected argument
=== Twice.

View File

@ -21,3 +21,11 @@ Trailing 2
// Trailing before end of document. // Trailing before end of document.
Trailing 3 \ Trailing 3 \
---
#let linebreak() = [
// Inside the old line break definition is still active.
#circle(radius: 2pt, fill: #000) \
]
A \ B \ C \

View File

@ -0,0 +1,11 @@
// Test paragraph breaks.
---
// Paragraph breaks don't exist!
#let parbreak() = [ ]
No more
paragraph breaks
for you!

View File

@ -45,6 +45,14 @@ def hi():
print("Hi!") print("Hi!")
``` ```
---
// Make everything block-level.
#let raw(text) = raw(text, block: true)
// The new raw's argument list doesn't contain `block`.
// Error: 6-10 unexpected argument
This `is` block-level.
--- ---
// Unterminated. // Unterminated.
// Error: 2:1 expected backtick(s) // Error: 2:1 expected backtick(s)

View File

@ -10,5 +10,13 @@ Partly str*ength*ened.
// Scoped to body. // Scoped to body.
#rect[*Scoped] to body. #rect[*Scoped] to body.
// Unterminated is fine. ---
*The End #let strong = emph
*Emph*
#let strong() = "Bye"
*, *!
#let strong = 123
// Error: 1-2 expected function, found integer
*

View File

@ -209,7 +209,7 @@ fn test_part(
let (local_compare_ref, ref_diags) = parse_metadata(src, &map); let (local_compare_ref, ref_diags) = parse_metadata(src, &map);
let compare_ref = local_compare_ref.unwrap_or(compare_ref); let compare_ref = local_compare_ref.unwrap_or(compare_ref);
let mut scope = library::new(); let mut scope = library::_new();
let panics = Rc::new(RefCell::new(vec![])); let panics = Rc::new(RefCell::new(vec![]));
register_helpers(&mut scope, Rc::clone(&panics)); register_helpers(&mut scope, Rc::clone(&panics));