summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-03-21 17:46:09 +0100
committerLaurenz <laurmaedje@gmail.com>2021-03-21 17:50:56 +0100
commit5e08028fb36aa766957cba64c5c665edf9b96fb7 (patch)
tree912799dad3c1e25b7032f3e3bee009537c6f555b /src
parent898728f260923a91444eb23b522d0abf01a4299b (diff)
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
Diffstat (limited to 'src')
-rw-r--r--src/eval/capture.rs36
-rw-r--r--src/eval/mod.rs99
-rw-r--r--src/eval/ops.rs2
-rw-r--r--src/eval/value.rs13
-rw-r--r--src/exec/context.rs19
-rw-r--r--src/exec/mod.rs90
-rw-r--r--src/geom/relative.rs2
-rw-r--r--src/lib.rs4
-rw-r--r--src/library/align.rs2
-rw-r--r--src/library/base.rs30
-rw-r--r--src/library/markup.rs172
-rw-r--r--src/library/mod.rs22
-rw-r--r--src/library/page.rs2
-rw-r--r--src/library/par.rs4
-rw-r--r--src/library/spacing.rs4
-rw-r--r--src/main.rs2
-rw-r--r--src/parse/mod.rs58
-rw-r--r--src/parse/resolve.rs95
-rw-r--r--src/pretty.rs85
-rw-r--r--src/syntax/expr.rs98
-rw-r--r--src/syntax/node.rs121
-rw-r--r--src/syntax/token.rs2
-rw-r--r--src/syntax/visit.rs22
23 files changed, 592 insertions, 392 deletions
diff --git a/src/eval/capture.rs b/src/eval/capture.rs
index c0354cd3..bee523ef 100644
--- a/src/eval/capture.rs
+++ b/src/eval/capture.rs
@@ -2,7 +2,7 @@ use std::rc::Rc;
use super::{Scope, Scopes, Value};
use crate::syntax::visit::{visit_expr, Visit};
-use crate::syntax::{Expr, Ident};
+use crate::syntax::{Expr, Ident, Node};
/// A visitor that captures variable slots.
#[derive(Debug)]
@@ -26,20 +26,36 @@ impl<'a> CapturesVisitor<'a> {
pub fn finish(self) -> Scope {
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<'_> {
+ 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) {
match node {
- Expr::Ident(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::Ident(ident) => self.process(ident),
expr => visit_expr(self, expr),
}
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 6d8edf79..802e1347 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -20,23 +20,23 @@ use crate::geom::{Angle, Length, Relative};
use crate::syntax::visit::Visit;
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
/// 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 map = tree.eval(&mut ctx);
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`].
-/// Since the lifetime is erased, the tree could go out of scope while the hash
-/// map still lives. Although this could lead to lookup panics, it is not unsafe
-/// since the pointers are never dereferenced.
-pub type ExprMap = HashMap<*const Expr, Value>;
+/// The raw pointers point into the nodes contained in some [`Tree`]. Since the
+/// lifetime is erased, the tree could go out of scope while the hash map still
+/// lives. Although this could lead to lookup panics, it is not unsafe since the
+/// pointers are never dereferenced.
+pub type NodeMap = HashMap<*const Node, Value>;
/// The context for evaluation.
#[derive(Debug)]
@@ -75,23 +75,24 @@ pub trait Eval {
}
impl Eval for Tree {
- type Output = ExprMap;
+ type Output = NodeMap;
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
- struct ExprVisitor<'a, 'b> {
- map: ExprMap,
- ctx: &'a mut EvalContext<'b>,
- }
+ let mut map = NodeMap::new();
- impl<'ast> Visit<'ast> for ExprVisitor<'_, '_> {
- fn visit_expr(&mut self, node: &'ast Expr) {
- self.map.insert(node as *const _, node.eval(self.ctx));
- }
+ 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);
}
- let mut visitor = ExprVisitor { map: ExprMap::new(), ctx };
- visitor.visit_tree(self);
- visitor.map
+ map
}
}
@@ -99,46 +100,36 @@ impl Eval for Expr {
type Output = Value;
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
- match self {
- Self::Lit(lit) => lit.eval(ctx),
- Self::Ident(v) => match ctx.scopes.get(&v) {
+ match *self {
+ Self::None(_) => Value::None,
+ 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(),
None => {
ctx.diag(error!(v.span, "unknown variable"));
Value::Error
}
},
- 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::Closure(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::While(v) => v.eval(ctx),
- Self::For(v) => v.eval(ctx),
- }
- }
-}
-
-impl Eval for Lit {
- type Output = Value;
-
- fn eval(&self, _: &mut EvalContext) -> Self::Output {
- match self.kind {
- LitKind::None => Value::None,
- LitKind::Bool(v) => Value::Bool(v),
- LitKind::Int(v) => Value::Int(v),
- LitKind::Float(v) => Value::Float(v),
- LitKind::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
- LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
- LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
- LitKind::Color(v) => Value::Color(Color::Rgba(v)),
- LitKind::Str(ref v) => Value::Str(v.clone()),
+ Self::Array(ref v) => Value::Array(v.eval(ctx)),
+ Self::Dict(ref v) => Value::Dict(v.eval(ctx)),
+ Self::Template(ref v) => Value::Template(vec![v.eval(ctx)]),
+ Self::Group(ref v) => v.eval(ctx),
+ Self::Block(ref v) => v.eval(ctx),
+ Self::Call(ref v) => v.eval(ctx),
+ Self::Closure(ref v) => v.eval(ctx),
+ Self::Unary(ref v) => v.eval(ctx),
+ Self::Binary(ref v) => v.eval(ctx),
+ Self::Let(ref v) => v.eval(ctx),
+ Self::If(ref v) => v.eval(ctx),
+ Self::While(ref v) => v.eval(ctx),
+ Self::For(ref v) => v.eval(ctx),
}
}
}
diff --git a/src/eval/ops.rs b/src/eval/ops.rs
index bef4dd58..da3432a2 100644
--- a/src/eval/ops.rs
+++ b/src/eval/ops.rs
@@ -1,5 +1,4 @@
use super::{ArrayValue, DictValue, TemplateNode, Value};
-use crate::syntax::Span;
use 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(),
(Array(a), Array(b)) => array_eq(a, b),
(Dict(a), Dict(b)) => dict_eq(a, b),
- (Template(a), Template(b)) => Span::without_cmp(|| a == b),
(a, b) => a == b,
}
}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 485829f3..288e5ed7 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Deref;
use std::rc::Rc;
-use super::{EvalContext, ExprMap};
+use super::{EvalContext, NodeMap};
use crate::color::Color;
use crate::diag::DiagSet;
use crate::exec::ExecContext;
@@ -107,7 +107,7 @@ pub type TemplateValue = Vec<TemplateNode>;
///
/// Evaluating a template expression creates only a single node. Adding multiple
/// templates can yield multi-node templates.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone)]
pub enum TemplateNode {
/// A template that consists of a syntax tree plus already evaluated
/// expression.
@@ -115,7 +115,7 @@ pub enum TemplateNode {
/// The syntax tree of the corresponding template expression.
tree: Rc<Tree>,
/// The evaluated expressions for the `tree`.
- map: ExprMap,
+ map: NodeMap,
},
/// A template that was converted from a string.
Str(String),
@@ -123,6 +123,13 @@ pub enum TemplateNode {
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
/// behaviour.
#[derive(Clone)]
diff --git a/src/exec/context.rs b/src/exec/context.rs
index 761977fc..f19f6561 100644
--- a/src/exec/context.rs
+++ b/src/exec/context.rs
@@ -11,7 +11,7 @@ use crate::geom::{Dir, Gen, Linear, Sides, Size};
use crate::layout::{
Node, PadNode, PageRun, ParNode, SpacingNode, StackNode, TextNode, Tree,
};
-use crate::parse::is_newline;
+use crate::parse::{is_newline, Scanner};
use crate::syntax::{Span, Spanned};
/// The context for execution.
@@ -99,16 +99,19 @@ impl<'a> ExecContext<'a> {
///
/// The text is split into lines at newlines.
pub fn push_text(&mut self, text: &str) {
- let mut newline = false;
- for line in text.split_terminator(is_newline) {
- if newline {
+ let mut scanner = Scanner::new(text);
+ let mut line = String::new();
+
+ while let Some(c) = scanner.eat_merging_crlf() {
+ if is_newline(c) {
+ self.push(self.make_text_node(mem::take(&mut line)));
self.push_linebreak();
+ } else {
+ line.push(c);
}
-
- let node = self.make_text_node(line.into());
- self.push(node);
- newline = true;
}
+
+ self.push(self.make_text_node(line));
}
/// Apply a forced line break.
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index 5a2ff698..6f3b9c83 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -10,24 +10,24 @@ use std::rc::Rc;
use crate::diag::Pass;
use crate::env::Env;
-use crate::eval::{ExprMap, TemplateFunc, TemplateNode, TemplateValue, Value};
-use crate::layout::{self, FixedNode, SpacingNode, StackNode};
+use crate::eval::{NodeMap, TemplateFunc, TemplateNode, TemplateValue, Value};
+use crate::layout;
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
+/// 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
-/// as used for evaluation (no cloned version), because the expression map
-/// depends on the pointers being stable.
+/// as used for evaluation (no cloned version), because the node 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,
+ map: &NodeMap,
state: State,
) -> Pass<layout::Tree> {
let mut ctx = ExecContext::new(env, state);
@@ -47,14 +47,14 @@ pub trait Exec {
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 {
/// 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 {
- fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
+ fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) {
for node in self {
node.exec_with_map(ctx, map);
}
@@ -62,83 +62,15 @@ impl ExecWithMap for Tree {
}
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 {
Node::Text(text) => ctx.push_text(text),
Node::Space => ctx.push_space(),
- Node::Linebreak => ctx.push_linebreak(),
- 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),
+ _ => map[&(self 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 {
fn exec(&self, ctx: &mut ExecContext) {
match self {
diff --git a/src/geom/relative.rs b/src/geom/relative.rs
index 65312e99..bbae5aba 100644
--- a/src/geom/relative.rs
+++ b/src/geom/relative.rs
@@ -3,7 +3,7 @@ use super::*;
/// A relative length.
///
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
-/// corresponding [literal](crate::syntax::LitKind::Percent).
+/// corresponding [literal](crate::syntax::Expr::Percent).
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
pub struct Relative(f64);
diff --git a/src/lib.rs b/src/lib.rs
index d2e47c62..cc370a0a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,8 +6,8 @@
//! tree]. The structures describing the tree can be found in the [syntax]
//! module.
//! - **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.
+//! computes the value of each node in document and stores them in a map from
+//! node-pointers to values.
//! - **Execution:** Now, we can [execute] the parsed and evaluated "script".
//! This produces a [layout tree], a high-level, fully styled representation
//! of the document. The nodes of this tree are self-contained and
diff --git a/src/library/align.rs b/src/library/align.rs
index 93c6db0d..765ed988 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -1,6 +1,6 @@
use super::*;
-/// `align`: Align content along the layouting axes.
+/// `align`: Configure the alignment along the layouting axes.
///
/// # Positional parameters
/// - Alignments: variadic, of type `alignment`.
diff --git a/src/library/base.rs b/src/library/base.rs
index 22adb1f4..fdabdc47 100644
--- a/src/library/base.rs
+++ b/src/library/base.rs
@@ -3,6 +3,20 @@ use crate::pretty::pretty;
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.
///
/// # 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
/// - 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),
)))
}
-
-/// `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,
- }
-}
diff --git a/src/library/markup.rs b/src/library/markup.rs
new file mode 100644
index 00000000..12a14d14
--- /dev/null
+++ b/src/library/markup.rs
@@ -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;
+ })
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index b09f94a0..834c9625 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -1,12 +1,13 @@
//! The standard library.
//!
-//! Call [`new`] to obtain a [`Scope`] containing all standard library
+//! Call [`_new`] to obtain a [`Scope`] containing all standard library
//! definitions.
mod align;
mod base;
mod font;
mod image;
+mod markup;
mod pad;
mod page;
mod par;
@@ -17,6 +18,7 @@ pub use self::image::*;
pub use align::*;
pub use base::*;
pub use font::*;
+pub use markup::*;
pub use pad::*;
pub use page::*;
pub use par::*;
@@ -32,10 +34,10 @@ use crate::eval::{EvalContext, FuncArgs, TemplateValue, Value};
use crate::exec::{Exec, ExecContext};
use crate::geom::*;
use crate::layout::VerticalFontMetric;
-use crate::syntax::Spanned;
+use crate::syntax::{Node, Spanned};
/// Construct a scope containing all standard library definitions.
-pub fn new() -> Scope {
+pub fn _new() -> Scope {
let mut std = Scope::new();
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!("circle", circle);
func!("ellipse", ellipse);
@@ -59,14 +70,15 @@ pub fn new() -> Scope {
func!("pad", pad);
func!("page", page);
func!("pagebreak", pagebreak);
- func!("paragraph", par);
- func!("square", square);
+ func!("par", par);
func!("rect", rect);
func!("repr", repr);
func!("rgb", rgb);
+ func!("square", square);
func!("type", type_);
func!("v", v);
+ // Constants.
constant!("left", AlignValue::Left);
constant!("center", AlignValue::Center);
constant!("right", AlignValue::Right);
diff --git a/src/library/page.rs b/src/library/page.rs
index 067258f5..89722ba3 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -109,7 +109,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// `pagebreak`: Start a new page.
///
/// # 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 {
let span = args.span;
Value::template("pagebreak", move |ctx| {
diff --git a/src/library/par.rs b/src/library/par.rs
index a7db46de..0467af44 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -1,6 +1,6 @@
use super::*;
-/// `paragraph`: Configure paragraphs.
+/// `par`: Configure paragraphs.
///
/// # Positional parameters
/// - 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 body = args.find::<TemplateValue>(ctx);
- Value::template("paragraph", move |ctx| {
+ Value::template("par", move |ctx| {
let snapshot = ctx.state.clone();
if let Some(spacing) = spacing {
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index c96b8be4..5e3b7743 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -1,7 +1,7 @@
use super::*;
use crate::layout::SpacingNode;
-/// `h`: Add horizontal spacing.
+/// `h`: Insert horizontal spacing.
///
/// # Positional parameters
/// - 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)
}
-/// `v`: Add vertical spacing.
+/// `v`: Insert vertical spacing.
///
/// # Positional parameters
/// - Amount of spacing: of type `linear` relative to current font size.
diff --git a/src/main.rs b/src/main.rs
index 8b56512d..74ec743d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -44,7 +44,7 @@ fn main() -> anyhow::Result<()> {
resources: ResourceLoader::new(),
};
- let scope = library::new();
+ let scope = library::_new();
let state = State::default();
let Pass { output: frames, diags } = typeset(&mut env, &src, &scope, state);
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index ceb8a206..b4727fe9 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -31,7 +31,7 @@ fn tree(p: &mut Parser) -> Tree {
let mut tree = vec![];
while !p.eof() {
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;
}
tree.push(node);
@@ -43,19 +43,24 @@ fn tree(p: &mut Parser) -> Tree {
/// Parse a syntax node.
fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
let token = p.peek()?;
+ let span = p.peek_span();
let node = match token {
// Whitespace.
Token::Space(newlines) => {
*at_start |= newlines > 0;
- if newlines < 2 { Node::Space } else { Node::Parbreak }
+ if newlines < 2 {
+ Node::Space
+ } else {
+ Node::Parbreak(span)
+ }
}
// Text.
Token::Text(text) => Node::Text(text.into()),
// Markup.
- Token::Star => Node::Strong,
- Token::Underscore => Node::Emph,
+ Token::Star => Node::Strong(span),
+ Token::Underscore => Node::Emph(span),
Token::Eq => {
if *at_start {
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::Backslash => Node::Linebreak,
+ Token::Backslash => Node::Linebreak(span),
Token::Raw(t) => raw(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);
// Count depth.
- let mut level: usize = 0;
+ let mut level: usize = 1;
while p.eat_if(Token::Eq) {
level += 1;
}
- if level > 5 {
+ if level > 6 {
p.diag(warning!(start .. p.end(), "should not exceed depth 6"));
- level = 5;
+ level = 6;
}
// Parse the heading contents.
- let mut contents = vec![];
+ let mut tree = vec![];
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.
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 {
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.
fn literal(p: &mut Parser) -> Option<Expr> {
- let kind = match p.peek()? {
+ let span = p.peek_span();
+ let expr = 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({
+ Token::None => Expr::None(span),
+ Token::Bool(b) => Expr::Bool(span, b),
+ Token::Int(i) => Expr::Int(span, i),
+ Token::Float(f) => Expr::Float(span, f),
+ Token::Length(val, unit) => Expr::Length(span, val, unit),
+ Token::Angle(val, unit) => Expr::Angle(span, val, unit),
+ Token::Percent(p) => Expr::Percent(span, p),
+ Token::Color(color) => Expr::Color(span, color),
+ Token::Str(token) => Expr::Str(span, {
if !token.terminated {
p.expected_at("quote", p.peek_span().end);
}
@@ -298,7 +309,8 @@ fn literal(p: &mut Parser) -> Option<Expr> {
}),
_ => 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:
diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs
index 1f33198a..b3fdef4a 100644
--- a/src/parse/resolve.rs
+++ b/src/parse/resolve.rs
@@ -1,5 +1,5 @@
use super::{is_newline, Scanner};
-use crate::syntax::{Ident, Pos, RawNode};
+use crate::syntax::{Ident, RawNode, Span};
/// Resolve all escape sequences in a 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.
-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 {
let (tag, inner) = split_at_lang_tag(text);
- let (lines, had_newline) = trim_and_split_raw(inner);
- RawNode {
- lang: Ident::new(tag, start .. start + tag.len()),
- lines,
- block: had_newline,
- }
+ let (text, block) = trim_and_split_raw(inner);
+ let lang = Ident::new(tag, span.start .. span.start + tag.len());
+ RawNode { span, lang, text, block }
} else {
RawNode {
+ span,
lang: None,
- lines: split_lines(text),
+ text: split_lines(text).join("\n"),
block: false,
}
}
@@ -77,7 +75,7 @@ fn split_at_lang_tag(raw: &str) -> (&str, &str) {
/// Trim raw text and splits it into lines.
///
/// 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.
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 had_newline = lines.len() > 1;
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.
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, had_newline)
+ (lines.join("\n"), had_newline)
}
/// Split a string into a vector of lines
@@ -171,64 +169,53 @@ mod tests {
raw: &str,
backticks: usize,
lang: Option<&str>,
- lines: &[&str],
+ text: &str,
block: bool,
) {
- Span::without_cmp(|| assert_eq!(resolve_raw(raw, backticks, Pos(0)), RawNode {
- lang: lang.and_then(|id| Ident::new(id, 0)),
- lines: lines.iter().map(ToString::to_string).collect(),
- block,
- }));
+ Span::without_cmp(|| {
+ assert_eq!(resolve_raw(Span::ZERO, raw, backticks), RawNode {
+ span: Span::ZERO,
+ lang: lang.and_then(|id| Ident::new(id, 0)),
+ text: text.into(),
+ block,
+ });
+ });
}
// Just one backtick.
- test("py", 1, None, &["py"], false);
- test("1\n2", 1, None, &["1", "2"], false);
- test("1\r\n2", 1, None, &["1", "2"], false);
+ test("py", 1, None, "py", false);
+ test("1\n2", 1, None, "1\n2", false);
+ test("1\r\n2", 1, None, "1\n2", false);
// More than one backtick with lang tag.
- test("js alert()", 2, Some("js"), &["alert()"], false);
- test("py quit(\n\n)", 3, Some("py"), &["quit(", "", ")"], true);
- test("♥", 2, None, &[], false);
+ test("js alert()", 2, Some("js"), "alert()", false);
+ test("py quit(\n\n)", 3, Some("py"), "quit(\n\n)", true);
+ test("♥", 2, None, "", false);
// Trimming of whitespace (tested more thoroughly in separate test).
- test(" a", 2, None, &["a"], false);
- test(" a", 2, None, &[" a"], false);
- test(" \na", 2, None, &["a"], true);
+ test(" a", 2, None, "a", false);
+ test(" a", 2, None, " a", false);
+ test(" \na", 2, None, "a", true);
}
#[test]
fn test_trim_raw() {
#[track_caller]
- fn test(text: &str, expected: Vec<&str>) {
+ fn test(text: &str, expected: &str) {
assert_eq!(trim_and_split_raw(text).0, expected);
}
- test(" hi", vec!["hi"]);
- test(" hi", vec![" hi"]);
- test("\nhi", vec!["hi"]);
- test(" \n hi", vec![" hi"]);
- test("hi` ", vec!["hi`"]);
- test("hi` ", vec!["hi` "]);
- test("hi` ", vec!["hi` "]);
- test("hi ", vec!["hi "]);
- test("hi ", vec!["hi "]);
- test("hi\n", vec!["hi"]);
- test("hi \n ", vec!["hi "]);
- test(" \n hi \n ", vec![" 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"]);
+ test(" hi", "hi");
+ test(" hi", " hi");
+ test("\nhi", "hi");
+ test(" \n hi", " hi");
+ test("hi` ", "hi`");
+ test("hi` ", "hi` ");
+ test("hi` ", "hi` ");
+ test("hi ", "hi ");
+ test("hi ", "hi ");
+ test("hi\n", "hi");
+ test("hi \n ", "hi ");
+ test(" \n hi \n ", " hi ");
}
}
diff --git a/src/pretty.rs b/src/pretty.rs
index 1db54d10..4e03ed84 100644
--- a/src/pretty.rs
+++ b/src/pretty.rs
@@ -17,8 +17,8 @@ where
p.finish()
}
-/// Pretty print an item with an expression map and return the resulting string.
-pub fn pretty_with_map<T>(item: &T, map: &ExprMap) -> String
+/// Pretty print an item with a node map and return the resulting string.
+pub fn pretty_with_map<T>(item: &T, map: &NodeMap) -> String
where
T: PrettyWithMap + ?Sized,
{
@@ -33,10 +33,10 @@ pub trait Pretty {
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 {
/// 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
@@ -104,7 +104,7 @@ impl Write for Printer {
}
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 {
node.pretty_with_map(p, map);
}
@@ -112,20 +112,20 @@ impl PrettyWithMap for Tree {
}
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 {
- 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.
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::Raw(raw) => raw.pretty(p),
Self::Expr(expr) => {
if let Some(map) = map {
- let value = &map[&(expr as *const _)];
+ let value = &map[&(self as *const _)];
value.pretty(p);
} else {
if expr.has_short_form() {
@@ -139,8 +139,8 @@ impl PrettyWithMap for Node {
}
impl PrettyWithMap for HeadingNode {
- fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) {
- for _ in 0 ..= self.level {
+ fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
+ for _ in 0 .. self.level {
p.push('=');
}
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
- // backticks in the lines.
- let mut count;
- for line in &self.lines {
- count = 0;
- for c in line.chars() {
- if c == '`' {
- count += 1;
- backticks = backticks.max(3).max(count + 1);
- } else {
- count = 0;
- }
+ // backticks.
+ let mut count = 0;
+ for c in self.text.chars() {
+ if c == '`' {
+ count += 1;
+ backticks = backticks.max(3).max(count + 1);
+ } else {
+ count = 0;
}
}
@@ -190,12 +187,12 @@ impl Pretty for RawNode {
}
// The lines.
- p.join(&self.lines, "\n", |line, p| p.push_str(line));
+ p.push_str(&self.text);
// End untrimming.
if self.block {
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(' ');
}
@@ -209,7 +206,15 @@ impl Pretty for RawNode {
impl Pretty for Expr {
fn pretty(&self, p: &mut Printer) {
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::Array(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 {
fn pretty(&self, p: &mut Printer) {
p.push('(');
@@ -784,7 +767,7 @@ mod tests {
test_value(
vec![
TemplateNode::Tree {
- tree: Rc::new(vec![Node::Strong]),
+ tree: Rc::new(vec![Node::Strong(Span::ZERO)]),
map: HashMap::new(),
},
TemplateNode::Func(TemplateFunc::new("example", |_| {})),
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index 2c631991..97361fc3 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -7,8 +7,27 @@ use crate::geom::{AngularUnit, LengthUnit};
/// An expression.
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
- /// A literal, like `11pt` or `"hi"`.
- Lit(Lit),
+ /// The none literal: `none`.
+ 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`.
Ident(Ident),
/// An array expression: `(1, "hi", 12cm)`.
@@ -42,22 +61,30 @@ pub enum Expr {
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::Closure(v) => v.span,
- Self::Let(v) => v.span,
- Self::If(v) => v.span,
- Self::While(v) => v.span,
- Self::For(v) => v.span,
+ match *self {
+ Self::None(span) => span,
+ Self::Bool(span, _) => span,
+ Self::Int(span, _) => span,
+ Self::Float(span, _) => span,
+ Self::Length(span, _, _) => span,
+ Self::Angle(span, _, _) => span,
+ Self::Percent(span, _) => span,
+ Self::Color(span, _) => span,
+ Self::Str(span, _) => span,
+ Self::Ident(ref v) => v.span,
+ Self::Array(ref v) => v.span,
+ Self::Dict(ref v) => v.span,
+ Self::Template(ref v) => v.span,
+ Self::Group(ref 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)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ArrayExpr {
diff --git a/src/syntax/node.rs b/src/syntax/node.rs
index c94ee5b0..537a5686 100644
--- a/src/syntax/node.rs
+++ b/src/syntax/node.rs
@@ -1,35 +1,84 @@
+use std::rc::Rc;
+
use super::*;
/// A syntax node, encompassing a single logical entity of parsed source code.
#[derive(Debug, Clone, PartialEq)]
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.
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),
- /// An optionally syntax-highlighted raw block.
+ /// A raw block with optional syntax highlighting: `` `...` ``.
Raw(RawNode),
/// An expression.
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`.
#[derive(Debug, Clone, PartialEq)]
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,
/// 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: `` `...` ``.
@@ -97,12 +146,50 @@ pub struct HeadingNode {
/// whitespace simply by adding more spaces.
#[derive(Debug, Clone, PartialEq)]
pub struct RawNode {
+ /// The source code location.
+ pub span: Span,
/// An optional identifier specifying the language to syntax-highlight in.
pub lang: Option<Ident>,
- /// The lines of raw text, determined as the raw string between the
- /// backticks trimmed according to the above rules and split at newlines.
- pub lines: Vec<String>,
+ /// The raw text, determined as the raw string between the backticks trimmed
+ /// according to the above rules.
+ pub text: String,
/// Whether the element is block-level, that is, it has 3+ backticks
/// and contains at least one newline.
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() }
+}
diff --git a/src/syntax/token.rs b/src/syntax/token.rs
index 832c923d..40e1d6d2 100644
--- a/src/syntax/token.rs
+++ b/src/syntax/token.rs
@@ -121,7 +121,7 @@ pub enum Token<'s> {
/// A percentage: `50%`.
///
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
- /// [literal](super::LitKind::Percent).
+ /// [literal](super::Expr::Percent).
Percent(f64),
/// A color value: `#20d82a`.
Color(RgbaColor),
diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs
index 04546c5d..9c1272ee 100644
--- a/src/syntax/visit.rs
+++ b/src/syntax/visit.rs
@@ -50,13 +50,13 @@ visit! {
fn visit_node(v, node: &Node) {
match node {
- Node::Strong => {}
- Node::Emph => {}
- Node::Space => {}
- Node::Linebreak => {}
- Node::Parbreak => {}
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::Expr(expr) => v.visit_expr(expr),
}
@@ -64,7 +64,15 @@ visit! {
fn visit_expr(v, node: &Expr) {
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::Array(e) => v.visit_array(e),
Expr::Dict(e) => v.visit_dict(e),