diff --git a/bench/src/bench.rs b/bench/src/bench.rs index afb827b81..36b8b1376 100644 --- a/bench/src/bench.rs +++ b/bench/src/bench.rs @@ -28,7 +28,7 @@ fn benchmarks(c: &mut Criterion) { resources: ResourceLoader::new(), }; - let scope = library::new(); + let scope = library::_new(); let state = State::default(); // Prepare intermediate results and run warm. diff --git a/src/eval/capture.rs b/src/eval/capture.rs index c0354cd30..bee523ef7 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 6d8edf794..802e1347a 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 { +pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass { 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(); + + 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<'_, '_> { - 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 + 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 bef4dd58f..da3432a26 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 485829f39..288e5ed74 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; /// /// 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, /// 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 761977fcc..f19f6561b 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 { - self.push_linebreak(); - } + let mut scanner = Scanner::new(text); + let mut line = String::new(); - let node = self.make_text_node(line.into()); - self.push(node); - newline = true; + 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); + } } + + 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 5a2ff6984..6f3b9c837 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 { 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 65312e999..bbae5aba6 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 d2e47c62b..cc370a0a8 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 93c6db0dc..765ed9882 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 22adb1f46..fdabdc47f 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::(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::(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 000000000..12a14d142 --- /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::(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::(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::(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::(ctx, RawNode::TEXT).unwrap_or_default(); + let _lang = args.get::(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 b09f94a0c..834c96253 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 067258f5b..89722ba3b 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 a7db46de6..0467af44b 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::(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 c96b8be48..5e3b7743e 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 8b56512df..74ec743df 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 ceb8a206e..b4727fe96 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 { 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 { } } 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 { /// Parse a literal. fn literal(p: &mut Parser) -> Option { - 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 { }), _ => 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 1f33198a7..b3fdef4a0 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 { } /// 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, 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, 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, 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 1db54d10a..4e03ed849 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(item: &T, map: &ExprMap) -> String +/// Pretty print an item with a node map and return the resulting string. +pub fn pretty_with_map(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 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 2c631991f..97361fc38 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 c94ee5b01..537a5686a 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 { + 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, +} + +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, - /// 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, + /// 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 832c923dc..40e1d6d2b 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 04546c5db..9c1272eeb 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), diff --git a/tests/ref/markup/emph.png b/tests/ref/markup/emph.png index 75231a62c..f43eeecb7 100644 Binary files a/tests/ref/markup/emph.png and b/tests/ref/markup/emph.png differ diff --git a/tests/ref/markup/heading.png b/tests/ref/markup/heading.png index 3473f798a..515788f31 100644 Binary files a/tests/ref/markup/heading.png and b/tests/ref/markup/heading.png differ diff --git a/tests/ref/markup/linebreak.png b/tests/ref/markup/linebreak.png index 4e4702f9b..aa01c99a5 100644 Binary files a/tests/ref/markup/linebreak.png and b/tests/ref/markup/linebreak.png differ diff --git a/tests/ref/markup/parbreak.png b/tests/ref/markup/parbreak.png new file mode 100644 index 000000000..008afca25 Binary files /dev/null and b/tests/ref/markup/parbreak.png differ diff --git a/tests/ref/markup/raw.png b/tests/ref/markup/raw.png index 59a4e3dd6..1aebc02db 100644 Binary files a/tests/ref/markup/raw.png and b/tests/ref/markup/raw.png differ diff --git a/tests/ref/markup/strong.png b/tests/ref/markup/strong.png index 7e62f0f3f..20e29b1f7 100644 Binary files a/tests/ref/markup/strong.png and b/tests/ref/markup/strong.png differ diff --git a/tests/typ/expr/ops.typ b/tests/typ/expr/ops.typ index 2390b7b47..ef249c433 100644 --- a/tests/typ/expr/ops.typ +++ b/tests/typ/expr/ops.typ @@ -122,10 +122,7 @@ #test((1, 2, 3) == (1, 2.0) + (3,), true) #test((:) == (a: 1), false) #test((a: 2 - 1.0, b: 2) == (b: 2, a: 1), true) -#test([*Hi*] == [*Hi*], true) - #test("a" != "a", false) -#test([*] != [_], true) --- // Test comparison operators. diff --git a/tests/typ/library/paragraph.typ b/tests/typ/library/paragraph.typ index 3ce946bde..74fb81895 100644 --- a/tests/typ/library/paragraph.typ +++ b/tests/typ/library/paragraph.typ @@ -3,11 +3,11 @@ --- // 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 is the sun. --- // 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 ! diff --git a/tests/typ/markup/emph.typ b/tests/typ/markup/emph.typ index 500381c12..8e25e7d3c 100644 --- a/tests/typ/markup/emph.typ +++ b/tests/typ/markup/emph.typ @@ -10,5 +10,14 @@ Partly em_phas_ized. // 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 +_ diff --git a/tests/typ/markup/heading.typ b/tests/typ/markup/heading.typ index 15c391d1b..9bbe34151 100644 --- a/tests/typ/markup/heading.typ +++ b/tests/typ/markup/heading.typ @@ -42,3 +42,11 @@ No = heading // Escaped. \= 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. diff --git a/tests/typ/markup/linebreak.typ b/tests/typ/markup/linebreak.typ index e63929924..a0a501b65 100644 --- a/tests/typ/markup/linebreak.typ +++ b/tests/typ/markup/linebreak.typ @@ -21,3 +21,11 @@ Trailing 2 // Trailing before end of document. Trailing 3 \ + +--- +#let linebreak() = [ + // Inside the old line break definition is still active. + #circle(radius: 2pt, fill: #000) \ +] + +A \ B \ C \ diff --git a/tests/typ/markup/parbreak.typ b/tests/typ/markup/parbreak.typ new file mode 100644 index 000000000..b9b8a222d --- /dev/null +++ b/tests/typ/markup/parbreak.typ @@ -0,0 +1,11 @@ +// Test paragraph breaks. + +--- +// Paragraph breaks don't exist! +#let parbreak() = [ ] + +No more + +paragraph breaks + +for you! diff --git a/tests/typ/markup/raw.typ b/tests/typ/markup/raw.typ index 005d413ed..cae3d2618 100644 --- a/tests/typ/markup/raw.typ +++ b/tests/typ/markup/raw.typ @@ -45,6 +45,14 @@ def 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. // Error: 2:1 expected backtick(s) diff --git a/tests/typ/markup/strong.typ b/tests/typ/markup/strong.typ index a56e0e1cf..6a6fc04a7 100644 --- a/tests/typ/markup/strong.typ +++ b/tests/typ/markup/strong.typ @@ -10,5 +10,13 @@ Partly str*ength*ened. // 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 +* diff --git a/tests/typeset.rs b/tests/typeset.rs index 2cf6bfb6e..0a22c96be 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -209,7 +209,7 @@ fn test_part( let (local_compare_ref, ref_diags) = parse_metadata(src, &map); 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![])); register_helpers(&mut scope, Rc::clone(&panics));