From 5e08028fb36aa766957cba64c5c665edf9b96fb7 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 21 Mar 2021 17:46:09 +0100 Subject: [PATCH] =?UTF-8?q?Syntax=20functions=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- bench/src/bench.rs | 2 +- src/eval/capture.rs | 36 +++++-- src/eval/mod.rs | 101 +++++++++---------- src/eval/ops.rs | 2 - src/eval/value.rs | 13 ++- src/exec/context.rs | 21 ++-- src/exec/mod.rs | 90 ++--------------- src/geom/relative.rs | 2 +- src/lib.rs | 4 +- src/library/align.rs | 2 +- src/library/base.rs | 30 +++--- src/library/markup.rs | 172 ++++++++++++++++++++++++++++++++ src/library/mod.rs | 22 +++- src/library/page.rs | 2 +- src/library/par.rs | 4 +- src/library/spacing.rs | 4 +- src/main.rs | 2 +- src/parse/mod.rs | 58 ++++++----- src/parse/resolve.rs | 95 ++++++++---------- src/pretty.rs | 85 +++++++--------- src/syntax/expr.rs | 98 +++++++++--------- src/syntax/node.rs | 121 ++++++++++++++++++---- src/syntax/token.rs | 2 +- src/syntax/visit.rs | 22 ++-- tests/ref/markup/emph.png | Bin 3053 -> 3387 bytes tests/ref/markup/heading.png | Bin 4540 -> 5288 bytes tests/ref/markup/linebreak.png | Bin 3770 -> 4307 bytes tests/ref/markup/parbreak.png | Bin 0 -> 1654 bytes tests/ref/markup/raw.png | Bin 7291 -> 8299 bytes tests/ref/markup/strong.png | Bin 2824 -> 3374 bytes tests/typ/expr/ops.typ | 3 - tests/typ/library/paragraph.typ | 4 +- tests/typ/markup/emph.typ | 13 ++- tests/typ/markup/heading.typ | 8 ++ tests/typ/markup/linebreak.typ | 8 ++ tests/typ/markup/parbreak.typ | 11 ++ tests/typ/markup/raw.typ | 8 ++ tests/typ/markup/strong.typ | 12 ++- tests/typeset.rs | 2 +- 39 files changed, 654 insertions(+), 405 deletions(-) create mode 100644 src/library/markup.rs create mode 100644 tests/ref/markup/parbreak.png create mode 100644 tests/typ/markup/parbreak.typ 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 75231a62ce366eae041d3b62d86a174ed3d6b367..f43eeecb7e031712cca68cd4314855dd6dc98af8 100644 GIT binary patch literal 3387 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 diff --git a/tests/ref/markup/heading.png b/tests/ref/markup/heading.png index 3473f798acb2437002fcd65236d717831ea28f5e..515788f317014fa24b34d66061c3d4223a256fd5 100644 GIT binary patch literal 5288 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 literal 4540 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 diff --git a/tests/ref/markup/linebreak.png b/tests/ref/markup/linebreak.png index 4e4702f9b5d4f7f912d8ae8d9d0a22fcd6daa850..aa01c99a52334acfd17b368b5424a8533e57a320 100644 GIT binary patch literal 4307 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 diff --git a/tests/ref/markup/parbreak.png b/tests/ref/markup/parbreak.png new file mode 100644 index 0000000000000000000000000000000000000000..008afca259a626b6a8859b2d64ce915b86995ea4 GIT binary patch literal 1654 zcmV-+28sEJP)8N2BAS{5E_IAp+RU68iWR+L1+*fga)BO z_{tFSBOs((e%Ag&ocr(y2>u{4?i@@fop~lEX^`|7cQ7Jz9eZ7Rmi`ynPr2~V!~_x9 z_OI&6{NYg$K&(jk>JS7Yef%DT@5PDF#*pt`;*k(gdvGie@>x&k8XX{=Y?`Mj66R?x z4fZE}y6k1)bOJ7BTC2fz)?7||-F%+T0GA`{)j7)p?#0qXr`LLOwQ5Qsw}t9@)hI>@+%O?vBs6$3C^@G_S&H4Nm@lhv|YXgMxESQ#^T6O3%I zi5S&cqV|hyz@)**VM$7kgObFH4THpcYb(ZjU2gijAwcS6*&*cK1G(!f{~!ynhzHaW zutOQD7Q8F~i@6)SqH2X8kt`O_5`okQ5CcvUdx;zXoIsFpXN{ErXn70-qKGSBw^DBr zS0PBCw#N$4F92DN5X1xYgHlSFdk9$+Apl~lCCTr<%TL*AZ9=MdQq{WFvX6p*6Nw0* zWbe9t2SP1T_1Tf+KCvs^LWX*!yPhPmut|g@sn*`xP$2+a_b3QJkQ#~mR3PM!AS`bo z)KPN}p;y4w=sO5uHew+UW9GEf+5i`&Uqk5CqagqoISOFP2zNDOeG7pkBLJhWWpva1 z-N!PZ9cffKRR})g7w!qaXlI_6V2<5g?U`!f4)$31)x%v6ivO zoZ+QnE6JA~iq&g(@Zw51jCH|MA!w=YpsMwwAi%Yk(>@#KP7>g{Q_L@_HZq;%l!%j@ z6LFN6Oji9RN97=&WekO1KOJjeM5MpwRb>b=u6h{HA&#;pNHVT^=+Dl|>2gZ|SLrVq zHaV@P8|Q?o*2GcPOd93aGKC+7K(;=(x^zfo4VgA;Yovo1LWuik2q7fETf1c-g%CY1cG&B>&T`P5t(xiA%qM#8;CZ5s@D7M;(pWLIy?N|dL8fI z6F5T*%eV3%^?$8ko*e>nsbbVug-~+^K)bIB0myO!hy(vBge1q00vSe5x7$U(jru_9 zkLhoR0LfZw<43ik)r%YMroUa(oytLx>CG}c(M_b$=U_ioibU8f}ll!_)HMWS<5k#58?@#F#v}RH^3r0 zgsdZA6(o1>4zbIC77tETPIb000t5w&4hS;W1?Ij`l>96Z_CdLc1JwaInWkxyjm6hZ zPO-V{yxF^_S0Eg9@s08IK7IB1#n#3GV{LofHwwEk!P&6Cv`__p52IRqdF=s=%(tsP$Qn>GJPQQx7d0$P z0?cA{nJSd%0C5;BX7E8<$Gi1=#st6=AEQ>4uEO!S9wU?XHi!JGw?v%}X z@fkNfOUC$H(yULQ_*yQ&Uq@Q`1}h2La*xN5Xla{Qv*}07*qoM6N<$f{QEj A+W-In literal 0 HcmV?d00001 diff --git a/tests/ref/markup/raw.png b/tests/ref/markup/raw.png index 59a4e3dd616143b4569b6403e205432ad9a68d87..1aebc02db25150557e78b7db306301732bad315e 100644 GIT binary patch literal 8299 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 literal 7291 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 diff --git a/tests/ref/markup/strong.png b/tests/ref/markup/strong.png index 7e62f0f3f9d5d8ce913839f74156d9c5a0ff72e1..20e29b1f70cdf6fd803bd7cad693f72f33bf1fc7 100644 GIT binary patch literal 3374 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 literal 2824 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 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));