Syntax functions 🚀
This adds overridable functions that markup desugars into. Specifically: - \ desugars into linebreak - Two newlines desugar into parbreak - * desugars into strong - _ desugars into emph - = .. desugars into heading - `..` desugars into raw
@ -28,7 +28,7 @@ fn benchmarks(c: &mut Criterion) {
|
|||||||
resources: ResourceLoader::new(),
|
resources: ResourceLoader::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let scope = library::new();
|
let scope = library::_new();
|
||||||
let state = State::default();
|
let state = State::default();
|
||||||
|
|
||||||
// Prepare intermediate results and run warm.
|
// Prepare intermediate results and run warm.
|
||||||
|
@ -2,7 +2,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use super::{Scope, Scopes, Value};
|
use super::{Scope, Scopes, Value};
|
||||||
use crate::syntax::visit::{visit_expr, Visit};
|
use crate::syntax::visit::{visit_expr, Visit};
|
||||||
use crate::syntax::{Expr, Ident};
|
use crate::syntax::{Expr, Ident, Node};
|
||||||
|
|
||||||
/// A visitor that captures variable slots.
|
/// A visitor that captures variable slots.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -26,20 +26,36 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
pub fn finish(self) -> Scope {
|
pub fn finish(self) -> Scope {
|
||||||
self.captures
|
self.captures
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find out whether the name is not locally defined and if so if it can be
|
||||||
|
/// captured.
|
||||||
|
fn process(&mut self, name: &str) {
|
||||||
|
if self.internal.get(name).is_none() {
|
||||||
|
if let Some(slot) = self.external.get(name) {
|
||||||
|
self.captures.def_slot(name, Rc::clone(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ast> Visit<'ast> for CapturesVisitor<'_> {
|
impl<'ast> Visit<'ast> for CapturesVisitor<'_> {
|
||||||
|
fn visit_node(&mut self, node: &'ast Node) {
|
||||||
|
match node {
|
||||||
|
Node::Text(_) => {}
|
||||||
|
Node::Space => {}
|
||||||
|
Node::Linebreak(_) => self.process(Node::LINEBREAK),
|
||||||
|
Node::Parbreak(_) => self.process(Node::PARBREAK),
|
||||||
|
Node::Strong(_) => self.process(Node::STRONG),
|
||||||
|
Node::Emph(_) => self.process(Node::EMPH),
|
||||||
|
Node::Heading(_) => self.process(Node::HEADING),
|
||||||
|
Node::Raw(_) => self.process(Node::RAW),
|
||||||
|
Node::Expr(expr) => self.visit_expr(expr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_expr(&mut self, node: &'ast Expr) {
|
fn visit_expr(&mut self, node: &'ast Expr) {
|
||||||
match node {
|
match node {
|
||||||
Expr::Ident(ident) => {
|
Expr::Ident(ident) => self.process(ident),
|
||||||
// Find out whether the identifier is not locally defined, but
|
|
||||||
// captured, and if so, capture its value.
|
|
||||||
if self.internal.get(ident).is_none() {
|
|
||||||
if let Some(slot) = self.external.get(ident) {
|
|
||||||
self.captures.def_slot(ident.as_str(), Rc::clone(slot));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expr => visit_expr(self, expr),
|
expr => visit_expr(self, expr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
101
src/eval/mod.rs
@ -20,23 +20,23 @@ use crate::geom::{Angle, Length, Relative};
|
|||||||
use crate::syntax::visit::Visit;
|
use crate::syntax::visit::Visit;
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
|
|
||||||
/// Evaluate all expressions in a syntax tree.
|
/// Evaluate all nodes in a syntax tree.
|
||||||
///
|
///
|
||||||
/// The `scope` consists of the base definitions that are present from the
|
/// The `scope` consists of the base definitions that are present from the
|
||||||
/// beginning (typically, the standard library).
|
/// beginning (typically, the standard library).
|
||||||
pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass<ExprMap> {
|
pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass<NodeMap> {
|
||||||
let mut ctx = EvalContext::new(env, scope);
|
let mut ctx = EvalContext::new(env, scope);
|
||||||
let map = tree.eval(&mut ctx);
|
let map = tree.eval(&mut ctx);
|
||||||
Pass::new(map, ctx.diags)
|
Pass::new(map, ctx.diags)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A map from expressions to the values they evaluated to.
|
/// A map from nodes to the values they evaluated to.
|
||||||
///
|
///
|
||||||
/// The raw pointers point into the expressions contained in some [`Tree`].
|
/// The raw pointers point into the nodes contained in some [`Tree`]. Since the
|
||||||
/// Since the lifetime is erased, the tree could go out of scope while the hash
|
/// lifetime is erased, the tree could go out of scope while the hash map still
|
||||||
/// map still lives. Although this could lead to lookup panics, it is not unsafe
|
/// lives. Although this could lead to lookup panics, it is not unsafe since the
|
||||||
/// since the pointers are never dereferenced.
|
/// pointers are never dereferenced.
|
||||||
pub type ExprMap = HashMap<*const Expr, Value>;
|
pub type NodeMap = HashMap<*const Node, Value>;
|
||||||
|
|
||||||
/// The context for evaluation.
|
/// The context for evaluation.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -75,23 +75,24 @@ pub trait Eval {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for Tree {
|
impl Eval for Tree {
|
||||||
type Output = ExprMap;
|
type Output = NodeMap;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
struct ExprVisitor<'a, 'b> {
|
let mut map = NodeMap::new();
|
||||||
map: ExprMap,
|
|
||||||
ctx: &'a mut EvalContext<'b>,
|
for node in self {
|
||||||
|
let value = if let Some(call) = node.desugar() {
|
||||||
|
call.eval(ctx)
|
||||||
|
} else if let Node::Expr(expr) = node {
|
||||||
|
expr.eval(ctx)
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
map.insert(node as *const _, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ast> Visit<'ast> for ExprVisitor<'_, '_> {
|
map
|
||||||
fn visit_expr(&mut self, node: &'ast Expr) {
|
|
||||||
self.map.insert(node as *const _, node.eval(self.ctx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut visitor = ExprVisitor { map: ExprMap::new(), ctx };
|
|
||||||
visitor.visit_tree(self);
|
|
||||||
visitor.map
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,46 +100,36 @@ impl Eval for Expr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
match self {
|
match *self {
|
||||||
Self::Lit(lit) => lit.eval(ctx),
|
Self::None(_) => Value::None,
|
||||||
Self::Ident(v) => match ctx.scopes.get(&v) {
|
Self::Bool(_, v) => Value::Bool(v),
|
||||||
|
Self::Int(_, v) => Value::Int(v),
|
||||||
|
Self::Float(_, v) => Value::Float(v),
|
||||||
|
Self::Length(_, v, unit) => Value::Length(Length::with_unit(v, unit)),
|
||||||
|
Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)),
|
||||||
|
Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)),
|
||||||
|
Self::Color(_, v) => Value::Color(Color::Rgba(v)),
|
||||||
|
Self::Str(_, ref v) => Value::Str(v.clone()),
|
||||||
|
Self::Ident(ref v) => match ctx.scopes.get(&v) {
|
||||||
Some(slot) => slot.borrow().clone(),
|
Some(slot) => slot.borrow().clone(),
|
||||||
None => {
|
None => {
|
||||||
ctx.diag(error!(v.span, "unknown variable"));
|
ctx.diag(error!(v.span, "unknown variable"));
|
||||||
Value::Error
|
Value::Error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Self::Array(v) => Value::Array(v.eval(ctx)),
|
Self::Array(ref v) => Value::Array(v.eval(ctx)),
|
||||||
Self::Dict(v) => Value::Dict(v.eval(ctx)),
|
Self::Dict(ref v) => Value::Dict(v.eval(ctx)),
|
||||||
Self::Template(v) => Value::Template(vec![v.eval(ctx)]),
|
Self::Template(ref v) => Value::Template(vec![v.eval(ctx)]),
|
||||||
Self::Group(v) => v.eval(ctx),
|
Self::Group(ref v) => v.eval(ctx),
|
||||||
Self::Block(v) => v.eval(ctx),
|
Self::Block(ref v) => v.eval(ctx),
|
||||||
Self::Call(v) => v.eval(ctx),
|
Self::Call(ref v) => v.eval(ctx),
|
||||||
Self::Closure(v) => v.eval(ctx),
|
Self::Closure(ref v) => v.eval(ctx),
|
||||||
Self::Unary(v) => v.eval(ctx),
|
Self::Unary(ref v) => v.eval(ctx),
|
||||||
Self::Binary(v) => v.eval(ctx),
|
Self::Binary(ref v) => v.eval(ctx),
|
||||||
Self::Let(v) => v.eval(ctx),
|
Self::Let(ref v) => v.eval(ctx),
|
||||||
Self::If(v) => v.eval(ctx),
|
Self::If(ref v) => v.eval(ctx),
|
||||||
Self::While(v) => v.eval(ctx),
|
Self::While(ref v) => v.eval(ctx),
|
||||||
Self::For(v) => v.eval(ctx),
|
Self::For(ref v) => v.eval(ctx),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eval for Lit {
|
|
||||||
type Output = Value;
|
|
||||||
|
|
||||||
fn eval(&self, _: &mut EvalContext) -> Self::Output {
|
|
||||||
match self.kind {
|
|
||||||
LitKind::None => Value::None,
|
|
||||||
LitKind::Bool(v) => Value::Bool(v),
|
|
||||||
LitKind::Int(v) => Value::Int(v),
|
|
||||||
LitKind::Float(v) => Value::Float(v),
|
|
||||||
LitKind::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
|
|
||||||
LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
|
|
||||||
LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
|
|
||||||
LitKind::Color(v) => Value::Color(Color::Rgba(v)),
|
|
||||||
LitKind::Str(ref v) => Value::Str(v.clone()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use super::{ArrayValue, DictValue, TemplateNode, Value};
|
use super::{ArrayValue, DictValue, TemplateNode, Value};
|
||||||
use crate::syntax::Span;
|
|
||||||
use Value::*;
|
use Value::*;
|
||||||
|
|
||||||
/// Apply the plus operator to a value.
|
/// Apply the plus operator to a value.
|
||||||
@ -184,7 +183,6 @@ fn value_eq(lhs: &Value, rhs: &Value) -> bool {
|
|||||||
(&Linear(a), &Relative(b)) => a.rel == b && a.abs.is_zero(),
|
(&Linear(a), &Relative(b)) => a.rel == b && a.abs.is_zero(),
|
||||||
(Array(a), Array(b)) => array_eq(a, b),
|
(Array(a), Array(b)) => array_eq(a, b),
|
||||||
(Dict(a), Dict(b)) => dict_eq(a, b),
|
(Dict(a), Dict(b)) => dict_eq(a, b),
|
||||||
(Template(a), Template(b)) => Span::without_cmp(|| a == b),
|
|
||||||
(a, b) => a == b,
|
(a, b) => a == b,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Display, Formatter};
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::{EvalContext, ExprMap};
|
use super::{EvalContext, NodeMap};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::diag::DiagSet;
|
use crate::diag::DiagSet;
|
||||||
use crate::exec::ExecContext;
|
use crate::exec::ExecContext;
|
||||||
@ -107,7 +107,7 @@ pub type TemplateValue = Vec<TemplateNode>;
|
|||||||
///
|
///
|
||||||
/// Evaluating a template expression creates only a single node. Adding multiple
|
/// Evaluating a template expression creates only a single node. Adding multiple
|
||||||
/// templates can yield multi-node templates.
|
/// templates can yield multi-node templates.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TemplateNode {
|
pub enum TemplateNode {
|
||||||
/// A template that consists of a syntax tree plus already evaluated
|
/// A template that consists of a syntax tree plus already evaluated
|
||||||
/// expression.
|
/// expression.
|
||||||
@ -115,7 +115,7 @@ pub enum TemplateNode {
|
|||||||
/// The syntax tree of the corresponding template expression.
|
/// The syntax tree of the corresponding template expression.
|
||||||
tree: Rc<Tree>,
|
tree: Rc<Tree>,
|
||||||
/// The evaluated expressions for the `tree`.
|
/// The evaluated expressions for the `tree`.
|
||||||
map: ExprMap,
|
map: NodeMap,
|
||||||
},
|
},
|
||||||
/// A template that was converted from a string.
|
/// A template that was converted from a string.
|
||||||
Str(String),
|
Str(String),
|
||||||
@ -123,6 +123,13 @@ pub enum TemplateNode {
|
|||||||
Func(TemplateFunc),
|
Func(TemplateFunc),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TemplateNode {
|
||||||
|
fn eq(&self, _: &Self) -> bool {
|
||||||
|
// TODO: Figure out what we want here.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A reference-counted dynamic template node that can implement custom
|
/// A reference-counted dynamic template node that can implement custom
|
||||||
/// behaviour.
|
/// behaviour.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -11,7 +11,7 @@ use crate::geom::{Dir, Gen, Linear, Sides, Size};
|
|||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Node, PadNode, PageRun, ParNode, SpacingNode, StackNode, TextNode, Tree,
|
Node, PadNode, PageRun, ParNode, SpacingNode, StackNode, TextNode, Tree,
|
||||||
};
|
};
|
||||||
use crate::parse::is_newline;
|
use crate::parse::{is_newline, Scanner};
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
|
|
||||||
/// The context for execution.
|
/// The context for execution.
|
||||||
@ -99,16 +99,19 @@ impl<'a> ExecContext<'a> {
|
|||||||
///
|
///
|
||||||
/// The text is split into lines at newlines.
|
/// The text is split into lines at newlines.
|
||||||
pub fn push_text(&mut self, text: &str) {
|
pub fn push_text(&mut self, text: &str) {
|
||||||
let mut newline = false;
|
let mut scanner = Scanner::new(text);
|
||||||
for line in text.split_terminator(is_newline) {
|
let mut line = String::new();
|
||||||
if newline {
|
|
||||||
self.push_linebreak();
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = self.make_text_node(line.into());
|
while let Some(c) = scanner.eat_merging_crlf() {
|
||||||
self.push(node);
|
if is_newline(c) {
|
||||||
newline = true;
|
self.push(self.make_text_node(mem::take(&mut line)));
|
||||||
|
self.push_linebreak();
|
||||||
|
} else {
|
||||||
|
line.push(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.push(self.make_text_node(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a forced line break.
|
/// Apply a forced line break.
|
||||||
|
@ -10,24 +10,24 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use crate::diag::Pass;
|
use crate::diag::Pass;
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use crate::eval::{ExprMap, TemplateFunc, TemplateNode, TemplateValue, Value};
|
use crate::eval::{NodeMap, TemplateFunc, TemplateNode, TemplateValue, Value};
|
||||||
use crate::layout::{self, FixedNode, SpacingNode, StackNode};
|
use crate::layout;
|
||||||
use crate::pretty::pretty;
|
use crate::pretty::pretty;
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
|
|
||||||
/// Execute a syntax tree to produce a layout tree.
|
/// Execute a syntax tree to produce a layout tree.
|
||||||
///
|
///
|
||||||
/// The `map` shall be an expression map computed for this tree with
|
/// The `map` shall be a node map computed for this tree with
|
||||||
/// [`eval`](crate::eval::eval). Note that `tree` must be the _exact_ same tree
|
/// [`eval`](crate::eval::eval). Note that `tree` must be the _exact_ same tree
|
||||||
/// as used for evaluation (no cloned version), because the expression map
|
/// as used for evaluation (no cloned version), because the node map depends on
|
||||||
/// depends on the pointers being stable.
|
/// the pointers being stable.
|
||||||
///
|
///
|
||||||
/// The `state` is the base state that may be updated over the course of
|
/// The `state` is the base state that may be updated over the course of
|
||||||
/// execution.
|
/// execution.
|
||||||
pub fn exec(
|
pub fn exec(
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
tree: &Tree,
|
tree: &Tree,
|
||||||
map: &ExprMap,
|
map: &NodeMap,
|
||||||
state: State,
|
state: State,
|
||||||
) -> Pass<layout::Tree> {
|
) -> Pass<layout::Tree> {
|
||||||
let mut ctx = ExecContext::new(env, state);
|
let mut ctx = ExecContext::new(env, state);
|
||||||
@ -47,14 +47,14 @@ pub trait Exec {
|
|||||||
fn exec(&self, ctx: &mut ExecContext);
|
fn exec(&self, ctx: &mut ExecContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a node with an expression map that applies to it.
|
/// Execute a node with a node map that applies to it.
|
||||||
pub trait ExecWithMap {
|
pub trait ExecWithMap {
|
||||||
/// Execute the node.
|
/// Execute the node.
|
||||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap);
|
fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecWithMap for Tree {
|
impl ExecWithMap for Tree {
|
||||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) {
|
||||||
for node in self {
|
for node in self {
|
||||||
node.exec_with_map(ctx, map);
|
node.exec_with_map(ctx, map);
|
||||||
}
|
}
|
||||||
@ -62,83 +62,15 @@ impl ExecWithMap for Tree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExecWithMap for Node {
|
impl ExecWithMap for Node {
|
||||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) {
|
||||||
match self {
|
match self {
|
||||||
Node::Text(text) => ctx.push_text(text),
|
Node::Text(text) => ctx.push_text(text),
|
||||||
Node::Space => ctx.push_space(),
|
Node::Space => ctx.push_space(),
|
||||||
Node::Linebreak => ctx.push_linebreak(),
|
_ => map[&(self as *const _)].exec(ctx),
|
||||||
Node::Parbreak => ctx.push_parbreak(),
|
|
||||||
Node::Strong => ctx.state.font.strong ^= true,
|
|
||||||
Node::Emph => ctx.state.font.emph ^= true,
|
|
||||||
Node::Heading(heading) => heading.exec_with_map(ctx, map),
|
|
||||||
Node::Raw(raw) => raw.exec(ctx),
|
|
||||||
Node::Expr(expr) => map[&(expr as *const _)].exec(ctx),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecWithMap for HeadingNode {
|
|
||||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
|
||||||
let prev = ctx.state.clone();
|
|
||||||
let upscale = 1.5 - 0.1 * self.level as f64;
|
|
||||||
ctx.state.font.scale *= upscale;
|
|
||||||
ctx.state.font.strong = true;
|
|
||||||
|
|
||||||
self.contents.exec_with_map(ctx, map);
|
|
||||||
ctx.push_parbreak();
|
|
||||||
|
|
||||||
ctx.state = prev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Exec for RawNode {
|
|
||||||
fn exec(&self, ctx: &mut ExecContext) {
|
|
||||||
let prev = Rc::clone(&ctx.state.font.families);
|
|
||||||
ctx.set_monospace();
|
|
||||||
|
|
||||||
let em = ctx.state.font.font_size();
|
|
||||||
let leading = ctx.state.par.leading.resolve(em);
|
|
||||||
|
|
||||||
let mut children = vec![];
|
|
||||||
let mut newline = false;
|
|
||||||
for line in &self.lines {
|
|
||||||
if newline {
|
|
||||||
children.push(layout::Node::Spacing(SpacingNode {
|
|
||||||
amount: leading,
|
|
||||||
softness: 2,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
children.push(layout::Node::Text(ctx.make_text_node(line.clone())));
|
|
||||||
newline = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.block {
|
|
||||||
ctx.push_parbreak();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is wrapped in a fixed node to make sure the stack fits to its
|
|
||||||
// content instead of filling the available area.
|
|
||||||
ctx.push(FixedNode {
|
|
||||||
width: None,
|
|
||||||
height: None,
|
|
||||||
aspect: None,
|
|
||||||
child: StackNode {
|
|
||||||
dirs: ctx.state.dirs,
|
|
||||||
aligns: ctx.state.aligns,
|
|
||||||
children,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if self.block {
|
|
||||||
ctx.push_parbreak();
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.state.font.families = prev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Exec for Value {
|
impl Exec for Value {
|
||||||
fn exec(&self, ctx: &mut ExecContext) {
|
fn exec(&self, ctx: &mut ExecContext) {
|
||||||
match self {
|
match self {
|
||||||
|
@ -3,7 +3,7 @@ use super::*;
|
|||||||
/// A relative length.
|
/// A relative length.
|
||||||
///
|
///
|
||||||
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
|
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
|
||||||
/// corresponding [literal](crate::syntax::LitKind::Percent).
|
/// corresponding [literal](crate::syntax::Expr::Percent).
|
||||||
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
|
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
|
||||||
pub struct Relative(f64);
|
pub struct Relative(f64);
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
//! tree]. The structures describing the tree can be found in the [syntax]
|
//! tree]. The structures describing the tree can be found in the [syntax]
|
||||||
//! module.
|
//! module.
|
||||||
//! - **Evaluation:** The next step is to [evaluate] the syntax tree. This
|
//! - **Evaluation:** The next step is to [evaluate] the syntax tree. This
|
||||||
//! computes the value of each expression in document and stores them in a map
|
//! computes the value of each node in document and stores them in a map from
|
||||||
//! from expression-pointers to values.
|
//! node-pointers to values.
|
||||||
//! - **Execution:** Now, we can [execute] the parsed and evaluated "script".
|
//! - **Execution:** Now, we can [execute] the parsed and evaluated "script".
|
||||||
//! This produces a [layout tree], a high-level, fully styled representation
|
//! This produces a [layout tree], a high-level, fully styled representation
|
||||||
//! of the document. The nodes of this tree are self-contained and
|
//! of the document. The nodes of this tree are self-contained and
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// `align`: Align content along the layouting axes.
|
/// `align`: Configure the alignment along the layouting axes.
|
||||||
///
|
///
|
||||||
/// # Positional parameters
|
/// # Positional parameters
|
||||||
/// - Alignments: variadic, of type `alignment`.
|
/// - Alignments: variadic, of type `alignment`.
|
||||||
|
@ -3,6 +3,20 @@ use crate::pretty::pretty;
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// `type`: Get the name of a value's type.
|
||||||
|
///
|
||||||
|
/// # Positional parameters
|
||||||
|
/// - Any value.
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
/// The name of the value's type as a string.
|
||||||
|
pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
|
match args.require::<Value>(ctx, "value") {
|
||||||
|
Some(value) => value.type_name().into(),
|
||||||
|
None => Value::Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `repr`: Get the string representation of a value.
|
/// `repr`: Get the string representation of a value.
|
||||||
///
|
///
|
||||||
/// # Positional parameters
|
/// # Positional parameters
|
||||||
@ -17,7 +31,7 @@ pub fn repr(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `rgb`: Create an RGB(A) color.
|
/// `rgb`: Construct an RGB(A) color.
|
||||||
///
|
///
|
||||||
/// # Positional parameters
|
/// # Positional parameters
|
||||||
/// - Red component: of type `float`, between 0.0 and 1.0.
|
/// - Red component: of type `float`, between 0.0 and 1.0.
|
||||||
@ -49,17 +63,3 @@ pub fn rgb(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
|||||||
clamp(a, 255),
|
clamp(a, 255),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `type`: Find out the name of a value's type.
|
|
||||||
///
|
|
||||||
/// # Positional parameters
|
|
||||||
/// - Any value.
|
|
||||||
///
|
|
||||||
/// # Return value
|
|
||||||
/// The name of the value's type as a string.
|
|
||||||
pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
|
||||||
match args.require::<Value>(ctx, "value") {
|
|
||||||
Some(value) => value.type_name().into(),
|
|
||||||
None => Value::Error,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
172
src/library/markup.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::syntax::{HeadingNode, RawNode};
|
||||||
|
|
||||||
|
/// `linebreak`: Start a new line.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
/// This function has dedicated syntax:
|
||||||
|
/// ```typst
|
||||||
|
/// This line ends here, \
|
||||||
|
/// And a new one begins.
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
/// A template that inserts a line break.
|
||||||
|
pub fn linebreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value {
|
||||||
|
Value::template(Node::LINEBREAK, move |ctx| {
|
||||||
|
ctx.push_linebreak();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `parbreak`: Start a new paragraph.
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
/// A template that inserts a paragraph break.
|
||||||
|
pub fn parbreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value {
|
||||||
|
Value::template(Node::PARBREAK, move |ctx| {
|
||||||
|
ctx.push_parbreak();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `strong`: Signify important text by setting it in bold.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
/// This function has dedicated syntax.
|
||||||
|
/// ```typst
|
||||||
|
/// This is *important*!
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Positional parameters
|
||||||
|
/// - Body: optional, of type `template`.
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
/// A template that flips the strongness of text. The effect is scoped to the
|
||||||
|
/// body if present.
|
||||||
|
pub fn strong(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
|
let body = args.find::<TemplateValue>(ctx);
|
||||||
|
Value::template(Node::STRONG, move |ctx| {
|
||||||
|
let snapshot = ctx.state.clone();
|
||||||
|
ctx.state.font.strong ^= true;
|
||||||
|
|
||||||
|
if let Some(body) = &body {
|
||||||
|
body.exec(ctx);
|
||||||
|
ctx.state = snapshot;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `emph`: Emphasize text by setting it in italics.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
/// This function has dedicated syntax.
|
||||||
|
/// ```typst
|
||||||
|
/// I would have _never_ thought so!
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Positional parameters
|
||||||
|
/// - Body: optional, of type `template`.
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
/// A template that flips whether text is emphasized. The effect is scoped to
|
||||||
|
/// the body if present.
|
||||||
|
pub fn emph(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
|
let body = args.find::<TemplateValue>(ctx);
|
||||||
|
Value::template(Node::EMPH, move |ctx| {
|
||||||
|
let snapshot = ctx.state.clone();
|
||||||
|
ctx.state.font.emph ^= true;
|
||||||
|
|
||||||
|
if let Some(body) = &body {
|
||||||
|
body.exec(ctx);
|
||||||
|
ctx.state = snapshot;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `heading`: A section heading.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
/// This function has dedicated syntax.
|
||||||
|
/// ```typst
|
||||||
|
/// = Section
|
||||||
|
/// ...
|
||||||
|
///
|
||||||
|
/// == Subsection
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Positional parameters
|
||||||
|
/// - Body, of type `template`.
|
||||||
|
///
|
||||||
|
/// # Named parameters
|
||||||
|
/// - Section depth: `level`, of type `integer` between 1 and 6.
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
/// A template that sets the body as a section heading, that is, large and in
|
||||||
|
/// bold.
|
||||||
|
pub fn heading(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
|
let level = args.get(ctx, HeadingNode::LEVEL).unwrap_or(1);
|
||||||
|
let body = args
|
||||||
|
.require::<TemplateValue>(ctx, HeadingNode::BODY)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Value::template(Node::HEADING, move |ctx| {
|
||||||
|
let snapshot = ctx.state.clone();
|
||||||
|
let upscale = 1.6 - 0.1 * level as f64;
|
||||||
|
ctx.state.font.scale *= upscale;
|
||||||
|
ctx.state.font.strong = true;
|
||||||
|
|
||||||
|
body.exec(ctx);
|
||||||
|
ctx.push_parbreak();
|
||||||
|
|
||||||
|
ctx.state = snapshot;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `raw`: Raw text.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
/// This function has dedicated syntax:
|
||||||
|
/// - For inline-level raw text:
|
||||||
|
/// ```typst
|
||||||
|
/// `...`
|
||||||
|
/// ```
|
||||||
|
/// - For block-level raw text:
|
||||||
|
/// ````typst
|
||||||
|
/// ```rust
|
||||||
|
/// println!("Hello World!");
|
||||||
|
/// ```
|
||||||
|
/// ````
|
||||||
|
///
|
||||||
|
/// # Positional parameters
|
||||||
|
/// - Text, of type `string`.
|
||||||
|
///
|
||||||
|
/// # Named parameters
|
||||||
|
/// - Language for syntax highlighting: `lang`, of type `string`.
|
||||||
|
/// - Whether the item is block level (split in its own paragraph): `block`, of
|
||||||
|
/// type `boolean`.
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
/// A template that sets the text raw, that is, in monospace with optional
|
||||||
|
/// syntax highlighting.
|
||||||
|
pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
|
let text = args.require::<String>(ctx, RawNode::TEXT).unwrap_or_default();
|
||||||
|
let _lang = args.get::<String>(ctx, RawNode::LANG);
|
||||||
|
let block = args.get(ctx, RawNode::BLOCK).unwrap_or(false);
|
||||||
|
|
||||||
|
Value::template(Node::RAW, move |ctx| {
|
||||||
|
let snapshot = ctx.state.clone();
|
||||||
|
|
||||||
|
if block {
|
||||||
|
ctx.push_parbreak();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.set_monospace();
|
||||||
|
ctx.push_text(&text);
|
||||||
|
|
||||||
|
if block {
|
||||||
|
ctx.push_parbreak();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.state = snapshot;
|
||||||
|
})
|
||||||
|
}
|
@ -1,12 +1,13 @@
|
|||||||
//! The standard library.
|
//! The standard library.
|
||||||
//!
|
//!
|
||||||
//! Call [`new`] to obtain a [`Scope`] containing all standard library
|
//! Call [`_new`] to obtain a [`Scope`] containing all standard library
|
||||||
//! definitions.
|
//! definitions.
|
||||||
|
|
||||||
mod align;
|
mod align;
|
||||||
mod base;
|
mod base;
|
||||||
mod font;
|
mod font;
|
||||||
mod image;
|
mod image;
|
||||||
|
mod markup;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod page;
|
mod page;
|
||||||
mod par;
|
mod par;
|
||||||
@ -17,6 +18,7 @@ pub use self::image::*;
|
|||||||
pub use align::*;
|
pub use align::*;
|
||||||
pub use base::*;
|
pub use base::*;
|
||||||
pub use font::*;
|
pub use font::*;
|
||||||
|
pub use markup::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use page::*;
|
pub use page::*;
|
||||||
pub use par::*;
|
pub use par::*;
|
||||||
@ -32,10 +34,10 @@ use crate::eval::{EvalContext, FuncArgs, TemplateValue, Value};
|
|||||||
use crate::exec::{Exec, ExecContext};
|
use crate::exec::{Exec, ExecContext};
|
||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
use crate::layout::VerticalFontMetric;
|
use crate::layout::VerticalFontMetric;
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::{Node, Spanned};
|
||||||
|
|
||||||
/// Construct a scope containing all standard library definitions.
|
/// Construct a scope containing all standard library definitions.
|
||||||
pub fn new() -> Scope {
|
pub fn _new() -> Scope {
|
||||||
let mut std = Scope::new();
|
let mut std = Scope::new();
|
||||||
|
|
||||||
macro_rules! func {
|
macro_rules! func {
|
||||||
@ -50,6 +52,15 @@ pub fn new() -> Scope {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Syntax functions.
|
||||||
|
func!(Node::EMPH, emph);
|
||||||
|
func!(Node::HEADING, heading);
|
||||||
|
func!(Node::STRONG, strong);
|
||||||
|
func!(Node::RAW, raw);
|
||||||
|
func!(Node::LINEBREAK, linebreak);
|
||||||
|
func!(Node::PARBREAK, parbreak);
|
||||||
|
|
||||||
|
// Library functions.
|
||||||
func!("align", align);
|
func!("align", align);
|
||||||
func!("circle", circle);
|
func!("circle", circle);
|
||||||
func!("ellipse", ellipse);
|
func!("ellipse", ellipse);
|
||||||
@ -59,14 +70,15 @@ pub fn new() -> Scope {
|
|||||||
func!("pad", pad);
|
func!("pad", pad);
|
||||||
func!("page", page);
|
func!("page", page);
|
||||||
func!("pagebreak", pagebreak);
|
func!("pagebreak", pagebreak);
|
||||||
func!("paragraph", par);
|
func!("par", par);
|
||||||
func!("square", square);
|
|
||||||
func!("rect", rect);
|
func!("rect", rect);
|
||||||
func!("repr", repr);
|
func!("repr", repr);
|
||||||
func!("rgb", rgb);
|
func!("rgb", rgb);
|
||||||
|
func!("square", square);
|
||||||
func!("type", type_);
|
func!("type", type_);
|
||||||
func!("v", v);
|
func!("v", v);
|
||||||
|
|
||||||
|
// Constants.
|
||||||
constant!("left", AlignValue::Left);
|
constant!("left", AlignValue::Left);
|
||||||
constant!("center", AlignValue::Center);
|
constant!("center", AlignValue::Center);
|
||||||
constant!("right", AlignValue::Right);
|
constant!("right", AlignValue::Right);
|
||||||
|
@ -109,7 +109,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
|||||||
/// `pagebreak`: Start a new page.
|
/// `pagebreak`: Start a new page.
|
||||||
///
|
///
|
||||||
/// # Return value
|
/// # Return value
|
||||||
/// A template that starts a new page.
|
/// A template that inserts a page break.
|
||||||
pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||||
let span = args.span;
|
let span = args.span;
|
||||||
Value::template("pagebreak", move |ctx| {
|
Value::template("pagebreak", move |ctx| {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// `paragraph`: Configure paragraphs.
|
/// `par`: Configure paragraphs.
|
||||||
///
|
///
|
||||||
/// # Positional parameters
|
/// # Positional parameters
|
||||||
/// - Body: optional, of type `template`.
|
/// - Body: optional, of type `template`.
|
||||||
@ -19,7 +19,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
|||||||
let word_spacing = args.get(ctx, "word-spacing");
|
let word_spacing = args.get(ctx, "word-spacing");
|
||||||
let body = args.find::<TemplateValue>(ctx);
|
let body = args.find::<TemplateValue>(ctx);
|
||||||
|
|
||||||
Value::template("paragraph", move |ctx| {
|
Value::template("par", move |ctx| {
|
||||||
let snapshot = ctx.state.clone();
|
let snapshot = ctx.state.clone();
|
||||||
|
|
||||||
if let Some(spacing) = spacing {
|
if let Some(spacing) = spacing {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::layout::SpacingNode;
|
use crate::layout::SpacingNode;
|
||||||
|
|
||||||
/// `h`: Add horizontal spacing.
|
/// `h`: Insert horizontal spacing.
|
||||||
///
|
///
|
||||||
/// # Positional parameters
|
/// # Positional parameters
|
||||||
/// - Amount of spacing: of type `linear` relative to current font size.
|
/// - Amount of spacing: of type `linear` relative to current font size.
|
||||||
@ -12,7 +12,7 @@ pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
|||||||
spacing_impl(ctx, args, SpecAxis::Horizontal)
|
spacing_impl(ctx, args, SpecAxis::Horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `v`: Add vertical spacing.
|
/// `v`: Insert vertical spacing.
|
||||||
///
|
///
|
||||||
/// # Positional parameters
|
/// # Positional parameters
|
||||||
/// - Amount of spacing: of type `linear` relative to current font size.
|
/// - Amount of spacing: of type `linear` relative to current font size.
|
||||||
|
@ -44,7 +44,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
resources: ResourceLoader::new(),
|
resources: ResourceLoader::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let scope = library::new();
|
let scope = library::_new();
|
||||||
let state = State::default();
|
let state = State::default();
|
||||||
|
|
||||||
let Pass { output: frames, diags } = typeset(&mut env, &src, &scope, state);
|
let Pass { output: frames, diags } = typeset(&mut env, &src, &scope, state);
|
||||||
|
@ -31,7 +31,7 @@ fn tree(p: &mut Parser) -> Tree {
|
|||||||
let mut tree = vec![];
|
let mut tree = vec![];
|
||||||
while !p.eof() {
|
while !p.eof() {
|
||||||
if let Some(node) = node(p, &mut at_start) {
|
if let Some(node) = node(p, &mut at_start) {
|
||||||
if !matches!(node, Node::Parbreak | Node::Space) {
|
if !matches!(node, Node::Parbreak(_) | Node::Space) {
|
||||||
at_start = false;
|
at_start = false;
|
||||||
}
|
}
|
||||||
tree.push(node);
|
tree.push(node);
|
||||||
@ -43,19 +43,24 @@ fn tree(p: &mut Parser) -> Tree {
|
|||||||
/// Parse a syntax node.
|
/// Parse a syntax node.
|
||||||
fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||||
let token = p.peek()?;
|
let token = p.peek()?;
|
||||||
|
let span = p.peek_span();
|
||||||
let node = match token {
|
let node = match token {
|
||||||
// Whitespace.
|
// Whitespace.
|
||||||
Token::Space(newlines) => {
|
Token::Space(newlines) => {
|
||||||
*at_start |= newlines > 0;
|
*at_start |= newlines > 0;
|
||||||
if newlines < 2 { Node::Space } else { Node::Parbreak }
|
if newlines < 2 {
|
||||||
|
Node::Space
|
||||||
|
} else {
|
||||||
|
Node::Parbreak(span)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text.
|
// Text.
|
||||||
Token::Text(text) => Node::Text(text.into()),
|
Token::Text(text) => Node::Text(text.into()),
|
||||||
|
|
||||||
// Markup.
|
// Markup.
|
||||||
Token::Star => Node::Strong,
|
Token::Star => Node::Strong(span),
|
||||||
Token::Underscore => Node::Emph,
|
Token::Underscore => Node::Emph(span),
|
||||||
Token::Eq => {
|
Token::Eq => {
|
||||||
if *at_start {
|
if *at_start {
|
||||||
return Some(heading(p));
|
return Some(heading(p));
|
||||||
@ -64,7 +69,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Token::Tilde => Node::Text("\u{00A0}".into()),
|
Token::Tilde => Node::Text("\u{00A0}".into()),
|
||||||
Token::Backslash => Node::Linebreak,
|
Token::Backslash => Node::Linebreak(span),
|
||||||
Token::Raw(t) => raw(p, t),
|
Token::Raw(t) => raw(p, t),
|
||||||
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
||||||
|
|
||||||
@ -120,28 +125,33 @@ fn heading(p: &mut Parser) -> Node {
|
|||||||
p.assert(Token::Eq);
|
p.assert(Token::Eq);
|
||||||
|
|
||||||
// Count depth.
|
// Count depth.
|
||||||
let mut level: usize = 0;
|
let mut level: usize = 1;
|
||||||
while p.eat_if(Token::Eq) {
|
while p.eat_if(Token::Eq) {
|
||||||
level += 1;
|
level += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if level > 5 {
|
if level > 6 {
|
||||||
p.diag(warning!(start .. p.end(), "should not exceed depth 6"));
|
p.diag(warning!(start .. p.end(), "should not exceed depth 6"));
|
||||||
level = 5;
|
level = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the heading contents.
|
// Parse the heading contents.
|
||||||
let mut contents = vec![];
|
let mut tree = vec![];
|
||||||
while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) {
|
while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) {
|
||||||
contents.extend(node(p, &mut false));
|
tree.extend(node(p, &mut false));
|
||||||
}
|
}
|
||||||
|
|
||||||
Node::Heading(HeadingNode { level, contents })
|
Node::Heading(HeadingNode {
|
||||||
|
span: p.span(start),
|
||||||
|
level,
|
||||||
|
contents: Rc::new(tree),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a raw block.
|
/// Handle a raw block.
|
||||||
fn raw(p: &mut Parser, token: RawToken) -> Node {
|
fn raw(p: &mut Parser, token: RawToken) -> Node {
|
||||||
let raw = resolve::resolve_raw(token.text, token.backticks, p.start());
|
let span = p.peek_span();
|
||||||
|
let raw = resolve::resolve_raw(span, token.text, token.backticks);
|
||||||
if !token.terminated {
|
if !token.terminated {
|
||||||
p.diag(error!(p.peek_span().end, "expected backtick(s)"));
|
p.diag(error!(p.peek_span().end, "expected backtick(s)"));
|
||||||
}
|
}
|
||||||
@ -280,17 +290,18 @@ fn primary(p: &mut Parser, atomic: bool) -> Option<Expr> {
|
|||||||
|
|
||||||
/// Parse a literal.
|
/// Parse a literal.
|
||||||
fn literal(p: &mut Parser) -> Option<Expr> {
|
fn literal(p: &mut Parser) -> Option<Expr> {
|
||||||
let kind = match p.peek()? {
|
let span = p.peek_span();
|
||||||
|
let expr = match p.peek()? {
|
||||||
// Basic values.
|
// Basic values.
|
||||||
Token::None => LitKind::None,
|
Token::None => Expr::None(span),
|
||||||
Token::Bool(b) => LitKind::Bool(b),
|
Token::Bool(b) => Expr::Bool(span, b),
|
||||||
Token::Int(i) => LitKind::Int(i),
|
Token::Int(i) => Expr::Int(span, i),
|
||||||
Token::Float(f) => LitKind::Float(f),
|
Token::Float(f) => Expr::Float(span, f),
|
||||||
Token::Length(val, unit) => LitKind::Length(val, unit),
|
Token::Length(val, unit) => Expr::Length(span, val, unit),
|
||||||
Token::Angle(val, unit) => LitKind::Angle(val, unit),
|
Token::Angle(val, unit) => Expr::Angle(span, val, unit),
|
||||||
Token::Percent(p) => LitKind::Percent(p),
|
Token::Percent(p) => Expr::Percent(span, p),
|
||||||
Token::Color(color) => LitKind::Color(color),
|
Token::Color(color) => Expr::Color(span, color),
|
||||||
Token::Str(token) => LitKind::Str({
|
Token::Str(token) => Expr::Str(span, {
|
||||||
if !token.terminated {
|
if !token.terminated {
|
||||||
p.expected_at("quote", p.peek_span().end);
|
p.expected_at("quote", p.peek_span().end);
|
||||||
}
|
}
|
||||||
@ -298,7 +309,8 @@ fn literal(p: &mut Parser) -> Option<Expr> {
|
|||||||
}),
|
}),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
Some(Expr::Lit(Lit { span: p.eat_span(), kind }))
|
p.eat();
|
||||||
|
Some(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse something that starts with a parenthesis, which can be either of:
|
/// Parse something that starts with a parenthesis, which can be either of:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::{is_newline, Scanner};
|
use super::{is_newline, Scanner};
|
||||||
use crate::syntax::{Ident, Pos, RawNode};
|
use crate::syntax::{Ident, RawNode, Span};
|
||||||
|
|
||||||
/// Resolve all escape sequences in a string.
|
/// Resolve all escape sequences in a string.
|
||||||
pub fn resolve_string(string: &str) -> String {
|
pub fn resolve_string(string: &str) -> String {
|
||||||
@ -47,19 +47,17 @@ pub fn resolve_hex(sequence: &str) -> Option<char> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the language tag and trims the raw text.
|
/// Resolve the language tag and trims the raw text.
|
||||||
pub fn resolve_raw(text: &str, backticks: usize, start: Pos) -> RawNode {
|
pub fn resolve_raw(span: Span, text: &str, backticks: usize) -> RawNode {
|
||||||
if backticks > 1 {
|
if backticks > 1 {
|
||||||
let (tag, inner) = split_at_lang_tag(text);
|
let (tag, inner) = split_at_lang_tag(text);
|
||||||
let (lines, had_newline) = trim_and_split_raw(inner);
|
let (text, block) = trim_and_split_raw(inner);
|
||||||
RawNode {
|
let lang = Ident::new(tag, span.start .. span.start + tag.len());
|
||||||
lang: Ident::new(tag, start .. start + tag.len()),
|
RawNode { span, lang, text, block }
|
||||||
lines,
|
|
||||||
block: had_newline,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
RawNode {
|
RawNode {
|
||||||
|
span,
|
||||||
lang: None,
|
lang: None,
|
||||||
lines: split_lines(text),
|
text: split_lines(text).join("\n"),
|
||||||
block: false,
|
block: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +75,7 @@ fn split_at_lang_tag(raw: &str) -> (&str, &str) {
|
|||||||
/// Trim raw text and splits it into lines.
|
/// Trim raw text and splits it into lines.
|
||||||
///
|
///
|
||||||
/// Returns whether at least one newline was contained in `raw`.
|
/// Returns whether at least one newline was contained in `raw`.
|
||||||
fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) {
|
fn trim_and_split_raw(mut raw: &str) -> (String, bool) {
|
||||||
// Trims one space at the start.
|
// Trims one space at the start.
|
||||||
raw = raw.strip_prefix(' ').unwrap_or(raw);
|
raw = raw.strip_prefix(' ').unwrap_or(raw);
|
||||||
|
|
||||||
@ -87,8 +85,8 @@ fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut lines = split_lines(raw);
|
let mut lines = split_lines(raw);
|
||||||
let had_newline = lines.len() > 1;
|
|
||||||
let is_whitespace = |line: &String| line.chars().all(char::is_whitespace);
|
let is_whitespace = |line: &String| line.chars().all(char::is_whitespace);
|
||||||
|
let had_newline = lines.len() > 1;
|
||||||
|
|
||||||
// Trims a sequence of whitespace followed by a newline at the start.
|
// Trims a sequence of whitespace followed by a newline at the start.
|
||||||
if lines.first().map_or(false, is_whitespace) {
|
if lines.first().map_or(false, is_whitespace) {
|
||||||
@ -100,7 +98,7 @@ fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) {
|
|||||||
lines.pop();
|
lines.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
(lines, had_newline)
|
(lines.join("\n"), had_newline)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split a string into a vector of lines
|
/// Split a string into a vector of lines
|
||||||
@ -171,64 +169,53 @@ mod tests {
|
|||||||
raw: &str,
|
raw: &str,
|
||||||
backticks: usize,
|
backticks: usize,
|
||||||
lang: Option<&str>,
|
lang: Option<&str>,
|
||||||
lines: &[&str],
|
text: &str,
|
||||||
block: bool,
|
block: bool,
|
||||||
) {
|
) {
|
||||||
Span::without_cmp(|| assert_eq!(resolve_raw(raw, backticks, Pos(0)), RawNode {
|
Span::without_cmp(|| {
|
||||||
lang: lang.and_then(|id| Ident::new(id, 0)),
|
assert_eq!(resolve_raw(Span::ZERO, raw, backticks), RawNode {
|
||||||
lines: lines.iter().map(ToString::to_string).collect(),
|
span: Span::ZERO,
|
||||||
block,
|
lang: lang.and_then(|id| Ident::new(id, 0)),
|
||||||
}));
|
text: text.into(),
|
||||||
|
block,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just one backtick.
|
// Just one backtick.
|
||||||
test("py", 1, None, &["py"], false);
|
test("py", 1, None, "py", false);
|
||||||
test("1\n2", 1, None, &["1", "2"], false);
|
test("1\n2", 1, None, "1\n2", false);
|
||||||
test("1\r\n2", 1, None, &["1", "2"], false);
|
test("1\r\n2", 1, None, "1\n2", false);
|
||||||
|
|
||||||
// More than one backtick with lang tag.
|
// More than one backtick with lang tag.
|
||||||
test("js alert()", 2, Some("js"), &["alert()"], false);
|
test("js alert()", 2, Some("js"), "alert()", false);
|
||||||
test("py quit(\n\n)", 3, Some("py"), &["quit(", "", ")"], true);
|
test("py quit(\n\n)", 3, Some("py"), "quit(\n\n)", true);
|
||||||
test("♥", 2, None, &[], false);
|
test("♥", 2, None, "", false);
|
||||||
|
|
||||||
// Trimming of whitespace (tested more thoroughly in separate test).
|
// Trimming of whitespace (tested more thoroughly in separate test).
|
||||||
test(" a", 2, None, &["a"], false);
|
test(" a", 2, None, "a", false);
|
||||||
test(" a", 2, None, &[" a"], false);
|
test(" a", 2, None, " a", false);
|
||||||
test(" \na", 2, None, &["a"], true);
|
test(" \na", 2, None, "a", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_trim_raw() {
|
fn test_trim_raw() {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(text: &str, expected: Vec<&str>) {
|
fn test(text: &str, expected: &str) {
|
||||||
assert_eq!(trim_and_split_raw(text).0, expected);
|
assert_eq!(trim_and_split_raw(text).0, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
test(" hi", vec!["hi"]);
|
test(" hi", "hi");
|
||||||
test(" hi", vec![" hi"]);
|
test(" hi", " hi");
|
||||||
test("\nhi", vec!["hi"]);
|
test("\nhi", "hi");
|
||||||
test(" \n hi", vec![" hi"]);
|
test(" \n hi", " hi");
|
||||||
test("hi` ", vec!["hi`"]);
|
test("hi` ", "hi`");
|
||||||
test("hi` ", vec!["hi` "]);
|
test("hi` ", "hi` ");
|
||||||
test("hi` ", vec!["hi` "]);
|
test("hi` ", "hi` ");
|
||||||
test("hi ", vec!["hi "]);
|
test("hi ", "hi ");
|
||||||
test("hi ", vec!["hi "]);
|
test("hi ", "hi ");
|
||||||
test("hi\n", vec!["hi"]);
|
test("hi\n", "hi");
|
||||||
test("hi \n ", vec!["hi "]);
|
test("hi \n ", "hi ");
|
||||||
test(" \n hi \n ", vec![" hi "]);
|
test(" \n hi \n ", " hi ");
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_split_lines() {
|
|
||||||
#[track_caller]
|
|
||||||
fn test(text: &str, expected: Vec<&str>) {
|
|
||||||
assert_eq!(split_lines(text), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
test("raw\ntext", vec!["raw", "text"]);
|
|
||||||
test("a\r\nb", vec!["a", "b"]);
|
|
||||||
test("a\n\nb", vec!["a", "", "b"]);
|
|
||||||
test("a\r\x0Bb", vec!["a", "", "b"]);
|
|
||||||
test("a\r\n\r\nb", vec!["a", "", "b"]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ where
|
|||||||
p.finish()
|
p.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pretty print an item with an expression map and return the resulting string.
|
/// Pretty print an item with a node map and return the resulting string.
|
||||||
pub fn pretty_with_map<T>(item: &T, map: &ExprMap) -> String
|
pub fn pretty_with_map<T>(item: &T, map: &NodeMap) -> String
|
||||||
where
|
where
|
||||||
T: PrettyWithMap + ?Sized,
|
T: PrettyWithMap + ?Sized,
|
||||||
{
|
{
|
||||||
@ -33,10 +33,10 @@ pub trait Pretty {
|
|||||||
fn pretty(&self, p: &mut Printer);
|
fn pretty(&self, p: &mut Printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pretty print an item with an expression map that applies to it.
|
/// Pretty print an item with a node map that applies to it.
|
||||||
pub trait PrettyWithMap {
|
pub trait PrettyWithMap {
|
||||||
/// Pretty print this item into the given printer.
|
/// Pretty print this item into the given printer.
|
||||||
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>);
|
fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Pretty for T
|
impl<T> Pretty for T
|
||||||
@ -104,7 +104,7 @@ impl Write for Printer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PrettyWithMap for Tree {
|
impl PrettyWithMap for Tree {
|
||||||
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) {
|
fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
|
||||||
for node in self {
|
for node in self {
|
||||||
node.pretty_with_map(p, map);
|
node.pretty_with_map(p, map);
|
||||||
}
|
}
|
||||||
@ -112,20 +112,20 @@ impl PrettyWithMap for Tree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PrettyWithMap for Node {
|
impl PrettyWithMap for Node {
|
||||||
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) {
|
fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Strong => p.push('*'),
|
|
||||||
Self::Emph => p.push('_'),
|
|
||||||
Self::Space => p.push(' '),
|
|
||||||
Self::Linebreak => p.push_str(r"\"),
|
|
||||||
Self::Parbreak => p.push_str("\n\n"),
|
|
||||||
// TODO: Handle escaping.
|
// TODO: Handle escaping.
|
||||||
Self::Text(text) => p.push_str(text),
|
Self::Text(text) => p.push_str(text),
|
||||||
|
Self::Space => p.push(' '),
|
||||||
|
Self::Strong(_) => p.push('*'),
|
||||||
|
Self::Emph(_) => p.push('_'),
|
||||||
|
Self::Linebreak(_) => p.push_str(r"\"),
|
||||||
|
Self::Parbreak(_) => p.push_str("\n\n"),
|
||||||
Self::Heading(heading) => heading.pretty_with_map(p, map),
|
Self::Heading(heading) => heading.pretty_with_map(p, map),
|
||||||
Self::Raw(raw) => raw.pretty(p),
|
Self::Raw(raw) => raw.pretty(p),
|
||||||
Self::Expr(expr) => {
|
Self::Expr(expr) => {
|
||||||
if let Some(map) = map {
|
if let Some(map) = map {
|
||||||
let value = &map[&(expr as *const _)];
|
let value = &map[&(self as *const _)];
|
||||||
value.pretty(p);
|
value.pretty(p);
|
||||||
} else {
|
} else {
|
||||||
if expr.has_short_form() {
|
if expr.has_short_form() {
|
||||||
@ -139,8 +139,8 @@ impl PrettyWithMap for Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PrettyWithMap for HeadingNode {
|
impl PrettyWithMap for HeadingNode {
|
||||||
fn pretty_with_map(&self, p: &mut Printer, map: Option<&ExprMap>) {
|
fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
|
||||||
for _ in 0 ..= self.level {
|
for _ in 0 .. self.level {
|
||||||
p.push('=');
|
p.push('=');
|
||||||
}
|
}
|
||||||
self.contents.pretty_with_map(p, map);
|
self.contents.pretty_with_map(p, map);
|
||||||
@ -158,17 +158,14 @@ impl Pretty for RawNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// More backticks may be required if there are lots of consecutive
|
// More backticks may be required if there are lots of consecutive
|
||||||
// backticks in the lines.
|
// backticks.
|
||||||
let mut count;
|
let mut count = 0;
|
||||||
for line in &self.lines {
|
for c in self.text.chars() {
|
||||||
count = 0;
|
if c == '`' {
|
||||||
for c in line.chars() {
|
count += 1;
|
||||||
if c == '`' {
|
backticks = backticks.max(3).max(count + 1);
|
||||||
count += 1;
|
} else {
|
||||||
backticks = backticks.max(3).max(count + 1);
|
count = 0;
|
||||||
} else {
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,12 +187,12 @@ impl Pretty for RawNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The lines.
|
// The lines.
|
||||||
p.join(&self.lines, "\n", |line, p| p.push_str(line));
|
p.push_str(&self.text);
|
||||||
|
|
||||||
// End untrimming.
|
// End untrimming.
|
||||||
if self.block {
|
if self.block {
|
||||||
p.push('\n');
|
p.push('\n');
|
||||||
} else if self.lines.last().map_or(false, |line| line.trim_end().ends_with('`')) {
|
} else if self.text.trim_end().ends_with('`') {
|
||||||
p.push(' ');
|
p.push(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +206,15 @@ impl Pretty for RawNode {
|
|||||||
impl Pretty for Expr {
|
impl Pretty for Expr {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
match self {
|
match self {
|
||||||
Self::Lit(v) => v.pretty(p),
|
Self::None(_) => p.push_str("none"),
|
||||||
|
Self::Bool(_, v) => v.pretty(p),
|
||||||
|
Self::Int(_, v) => v.pretty(p),
|
||||||
|
Self::Float(_, v) => v.pretty(p),
|
||||||
|
Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
||||||
|
Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
||||||
|
Self::Percent(_, v) => write!(p, "{}%", v).unwrap(),
|
||||||
|
Self::Color(_, v) => v.pretty(p),
|
||||||
|
Self::Str(_, v) => v.pretty(p),
|
||||||
Self::Ident(v) => v.pretty(p),
|
Self::Ident(v) => v.pretty(p),
|
||||||
Self::Array(v) => v.pretty(p),
|
Self::Array(v) => v.pretty(p),
|
||||||
Self::Dict(v) => v.pretty(p),
|
Self::Dict(v) => v.pretty(p),
|
||||||
@ -228,28 +233,6 @@ impl Pretty for Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pretty for Lit {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
self.kind.pretty(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for LitKind {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
match self {
|
|
||||||
Self::None => p.push_str("none"),
|
|
||||||
Self::Bool(v) => v.pretty(p),
|
|
||||||
Self::Int(v) => v.pretty(p),
|
|
||||||
Self::Float(v) => v.pretty(p),
|
|
||||||
Self::Length(v, u) => write!(p, "{}{}", v, u).unwrap(),
|
|
||||||
Self::Angle(v, u) => write!(p, "{}{}", v, u).unwrap(),
|
|
||||||
Self::Percent(v) => write!(p, "{}%", v).unwrap(),
|
|
||||||
Self::Color(v) => v.pretty(p),
|
|
||||||
Self::Str(v) => v.pretty(p),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for ArrayExpr {
|
impl Pretty for ArrayExpr {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
p.push('(');
|
p.push('(');
|
||||||
@ -784,7 +767,7 @@ mod tests {
|
|||||||
test_value(
|
test_value(
|
||||||
vec![
|
vec![
|
||||||
TemplateNode::Tree {
|
TemplateNode::Tree {
|
||||||
tree: Rc::new(vec![Node::Strong]),
|
tree: Rc::new(vec![Node::Strong(Span::ZERO)]),
|
||||||
map: HashMap::new(),
|
map: HashMap::new(),
|
||||||
},
|
},
|
||||||
TemplateNode::Func(TemplateFunc::new("example", |_| {})),
|
TemplateNode::Func(TemplateFunc::new("example", |_| {})),
|
||||||
|
@ -7,8 +7,27 @@ use crate::geom::{AngularUnit, LengthUnit};
|
|||||||
/// An expression.
|
/// An expression.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
/// A literal, like `11pt` or `"hi"`.
|
/// The none literal: `none`.
|
||||||
Lit(Lit),
|
None(Span),
|
||||||
|
/// A boolean literal: `true`, `false`.
|
||||||
|
Bool(Span, bool),
|
||||||
|
/// An integer literal: `120`.
|
||||||
|
Int(Span, i64),
|
||||||
|
/// A floating-point literal: `1.2`, `10e-4`.
|
||||||
|
Float(Span, f64),
|
||||||
|
/// A length literal: `12pt`, `3cm`.
|
||||||
|
Length(Span, f64, LengthUnit),
|
||||||
|
/// An angle literal: `1.5rad`, `90deg`.
|
||||||
|
Angle(Span, f64, AngularUnit),
|
||||||
|
/// A percent literal: `50%`.
|
||||||
|
///
|
||||||
|
/// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
|
||||||
|
/// corresponding [value](crate::geom::Relative).
|
||||||
|
Percent(Span, f64),
|
||||||
|
/// A color literal: `#ffccee`.
|
||||||
|
Color(Span, RgbaColor),
|
||||||
|
/// A string literal: `"hello!"`.
|
||||||
|
Str(Span, String),
|
||||||
/// An identifier: `left`.
|
/// An identifier: `left`.
|
||||||
Ident(Ident),
|
Ident(Ident),
|
||||||
/// An array expression: `(1, "hi", 12cm)`.
|
/// An array expression: `(1, "hi", 12cm)`.
|
||||||
@ -42,22 +61,30 @@ pub enum Expr {
|
|||||||
impl Expr {
|
impl Expr {
|
||||||
/// The source code location.
|
/// The source code location.
|
||||||
pub fn span(&self) -> Span {
|
pub fn span(&self) -> Span {
|
||||||
match self {
|
match *self {
|
||||||
Self::Lit(v) => v.span,
|
Self::None(span) => span,
|
||||||
Self::Ident(v) => v.span,
|
Self::Bool(span, _) => span,
|
||||||
Self::Array(v) => v.span,
|
Self::Int(span, _) => span,
|
||||||
Self::Dict(v) => v.span,
|
Self::Float(span, _) => span,
|
||||||
Self::Template(v) => v.span,
|
Self::Length(span, _, _) => span,
|
||||||
Self::Group(v) => v.span,
|
Self::Angle(span, _, _) => span,
|
||||||
Self::Block(v) => v.span,
|
Self::Percent(span, _) => span,
|
||||||
Self::Unary(v) => v.span,
|
Self::Color(span, _) => span,
|
||||||
Self::Binary(v) => v.span,
|
Self::Str(span, _) => span,
|
||||||
Self::Call(v) => v.span,
|
Self::Ident(ref v) => v.span,
|
||||||
Self::Closure(v) => v.span,
|
Self::Array(ref v) => v.span,
|
||||||
Self::Let(v) => v.span,
|
Self::Dict(ref v) => v.span,
|
||||||
Self::If(v) => v.span,
|
Self::Template(ref v) => v.span,
|
||||||
Self::While(v) => v.span,
|
Self::Group(ref v) => v.span,
|
||||||
Self::For(v) => v.span,
|
Self::Block(ref v) => v.span,
|
||||||
|
Self::Unary(ref v) => v.span,
|
||||||
|
Self::Binary(ref v) => v.span,
|
||||||
|
Self::Call(ref v) => v.span,
|
||||||
|
Self::Closure(ref v) => v.span,
|
||||||
|
Self::Let(ref v) => v.span,
|
||||||
|
Self::If(ref v) => v.span,
|
||||||
|
Self::While(ref v) => v.span,
|
||||||
|
Self::For(ref v) => v.span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,41 +101,6 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A literal, like `11pt` or `"hi"`.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct Lit {
|
|
||||||
/// The source code location.
|
|
||||||
pub span: Span,
|
|
||||||
/// The kind of literal.
|
|
||||||
pub kind: LitKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A kind of literal.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum LitKind {
|
|
||||||
/// The none literal: `none`.
|
|
||||||
None,
|
|
||||||
/// A boolean literal: `true`, `false`.
|
|
||||||
Bool(bool),
|
|
||||||
/// An integer literal: `120`.
|
|
||||||
Int(i64),
|
|
||||||
/// A floating-point literal: `1.2`, `10e-4`.
|
|
||||||
Float(f64),
|
|
||||||
/// A length literal: `12pt`, `3cm`.
|
|
||||||
Length(f64, LengthUnit),
|
|
||||||
/// An angle literal: `1.5rad`, `90deg`.
|
|
||||||
Angle(f64, AngularUnit),
|
|
||||||
/// A percent literal: `50%`.
|
|
||||||
///
|
|
||||||
/// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
|
|
||||||
/// corresponding [value](crate::geom::Relative).
|
|
||||||
Percent(f64),
|
|
||||||
/// A color literal: `#ffccee`.
|
|
||||||
Color(RgbaColor),
|
|
||||||
/// A string literal: `"hello!"`.
|
|
||||||
Str(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An array expression: `(1, "hi", 12cm)`.
|
/// An array expression: `(1, "hi", 12cm)`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ArrayExpr {
|
pub struct ArrayExpr {
|
||||||
|
@ -1,35 +1,84 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// A syntax node, encompassing a single logical entity of parsed source code.
|
/// A syntax node, encompassing a single logical entity of parsed source code.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
/// Strong text was enabled / disabled.
|
|
||||||
Strong,
|
|
||||||
/// Emphasized text was enabled / disabled.
|
|
||||||
Emph,
|
|
||||||
/// Whitespace containing less than two newlines.
|
|
||||||
Space,
|
|
||||||
/// A forced line break.
|
|
||||||
Linebreak,
|
|
||||||
/// A paragraph break.
|
|
||||||
Parbreak,
|
|
||||||
/// Plain text.
|
/// Plain text.
|
||||||
Text(String),
|
Text(String),
|
||||||
/// A section heading.
|
/// Whitespace containing less than two newlines.
|
||||||
|
Space,
|
||||||
|
/// A forced line break: `\`.
|
||||||
|
Linebreak(Span),
|
||||||
|
/// A paragraph break: Two or more newlines.
|
||||||
|
Parbreak(Span),
|
||||||
|
/// Strong text was enabled / disabled: `*`.
|
||||||
|
Strong(Span),
|
||||||
|
/// Emphasized text was enabled / disabled: `_`.
|
||||||
|
Emph(Span),
|
||||||
|
/// A section heading: `= Introduction`.
|
||||||
Heading(HeadingNode),
|
Heading(HeadingNode),
|
||||||
/// An optionally syntax-highlighted raw block.
|
/// A raw block with optional syntax highlighting: `` `...` ``.
|
||||||
Raw(RawNode),
|
Raw(RawNode),
|
||||||
/// An expression.
|
/// An expression.
|
||||||
Expr(Expr),
|
Expr(Expr),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
// The names of the corresponding library functions.
|
||||||
|
pub const LINEBREAK: &'static str = "linebreak";
|
||||||
|
pub const PARBREAK: &'static str = "parbreak";
|
||||||
|
pub const STRONG: &'static str = "strong";
|
||||||
|
pub const EMPH: &'static str = "emph";
|
||||||
|
pub const HEADING: &'static str = "heading";
|
||||||
|
pub const RAW: &'static str = "raw";
|
||||||
|
|
||||||
|
/// Desugar markup into a function call.
|
||||||
|
pub fn desugar(&self) -> Option<CallExpr> {
|
||||||
|
match *self {
|
||||||
|
Node::Text(_) => None,
|
||||||
|
Node::Space => None,
|
||||||
|
Node::Linebreak(span) => Some(call(span, Self::LINEBREAK)),
|
||||||
|
Node::Parbreak(span) => Some(call(span, Self::PARBREAK)),
|
||||||
|
Node::Strong(span) => Some(call(span, Self::STRONG)),
|
||||||
|
Node::Emph(span) => Some(call(span, Self::EMPH)),
|
||||||
|
Self::Heading(ref heading) => Some(heading.desugar()),
|
||||||
|
Self::Raw(ref raw) => Some(raw.desugar()),
|
||||||
|
Node::Expr(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A section heading: `= Introduction`.
|
/// A section heading: `= Introduction`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct HeadingNode {
|
pub struct HeadingNode {
|
||||||
/// The section depth (numer of equals signs minus 1).
|
/// The source code location.
|
||||||
|
pub span: Span,
|
||||||
|
/// The section depth (numer of equals signs).
|
||||||
pub level: usize,
|
pub level: usize,
|
||||||
/// The contents of the heading.
|
/// The contents of the heading.
|
||||||
pub contents: Tree,
|
pub contents: Rc<Tree>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeadingNode {
|
||||||
|
pub const LEVEL: &'static str = "level";
|
||||||
|
pub const BODY: &'static str = "body";
|
||||||
|
|
||||||
|
/// Desugar into a function call.
|
||||||
|
pub fn desugar(&self) -> CallExpr {
|
||||||
|
let Self { span, level, ref contents } = *self;
|
||||||
|
let mut call = call(span, Node::HEADING);
|
||||||
|
call.args.items.push(CallArg::Named(Named {
|
||||||
|
name: ident(span, Self::LEVEL),
|
||||||
|
expr: Expr::Int(span, level as i64),
|
||||||
|
}));
|
||||||
|
call.args.items.push(CallArg::Pos(Expr::Template(TemplateExpr {
|
||||||
|
span,
|
||||||
|
tree: Rc::clone(&contents),
|
||||||
|
})));
|
||||||
|
call
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A raw block with optional syntax highlighting: `` `...` ``.
|
/// A raw block with optional syntax highlighting: `` `...` ``.
|
||||||
@ -97,12 +146,50 @@ pub struct HeadingNode {
|
|||||||
/// whitespace simply by adding more spaces.
|
/// whitespace simply by adding more spaces.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct RawNode {
|
pub struct RawNode {
|
||||||
|
/// The source code location.
|
||||||
|
pub span: Span,
|
||||||
/// An optional identifier specifying the language to syntax-highlight in.
|
/// An optional identifier specifying the language to syntax-highlight in.
|
||||||
pub lang: Option<Ident>,
|
pub lang: Option<Ident>,
|
||||||
/// The lines of raw text, determined as the raw string between the
|
/// The raw text, determined as the raw string between the backticks trimmed
|
||||||
/// backticks trimmed according to the above rules and split at newlines.
|
/// according to the above rules.
|
||||||
pub lines: Vec<String>,
|
pub text: String,
|
||||||
/// Whether the element is block-level, that is, it has 3+ backticks
|
/// Whether the element is block-level, that is, it has 3+ backticks
|
||||||
/// and contains at least one newline.
|
/// and contains at least one newline.
|
||||||
pub block: bool,
|
pub block: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RawNode {
|
||||||
|
pub const LANG: &'static str = "lang";
|
||||||
|
pub const BLOCK: &'static str = "block";
|
||||||
|
pub const TEXT: &'static str = "text";
|
||||||
|
|
||||||
|
/// Desugar into a function call.
|
||||||
|
pub fn desugar(&self) -> CallExpr {
|
||||||
|
let Self { span, ref lang, ref text, block } = *self;
|
||||||
|
let mut call = call(span, Node::RAW);
|
||||||
|
if let Some(lang) = lang {
|
||||||
|
call.args.items.push(CallArg::Named(Named {
|
||||||
|
name: ident(span, Self::LANG),
|
||||||
|
expr: Expr::Str(span, lang.string.clone()),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
call.args.items.push(CallArg::Named(Named {
|
||||||
|
name: ident(span, Self::BLOCK),
|
||||||
|
expr: Expr::Bool(span, block),
|
||||||
|
}));
|
||||||
|
call.args.items.push(CallArg::Pos(Expr::Str(span, text.clone())));
|
||||||
|
call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(span: Span, name: &str) -> CallExpr {
|
||||||
|
CallExpr {
|
||||||
|
span,
|
||||||
|
callee: Box::new(Expr::Ident(Ident { span, string: name.into() })),
|
||||||
|
args: CallArgs { span, items: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ident(span: Span, string: &str) -> Ident {
|
||||||
|
Ident { span, string: string.into() }
|
||||||
|
}
|
||||||
|
@ -121,7 +121,7 @@ pub enum Token<'s> {
|
|||||||
/// A percentage: `50%`.
|
/// A percentage: `50%`.
|
||||||
///
|
///
|
||||||
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
|
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
|
||||||
/// [literal](super::LitKind::Percent).
|
/// [literal](super::Expr::Percent).
|
||||||
Percent(f64),
|
Percent(f64),
|
||||||
/// A color value: `#20d82a`.
|
/// A color value: `#20d82a`.
|
||||||
Color(RgbaColor),
|
Color(RgbaColor),
|
||||||
|
@ -50,13 +50,13 @@ visit! {
|
|||||||
|
|
||||||
fn visit_node(v, node: &Node) {
|
fn visit_node(v, node: &Node) {
|
||||||
match node {
|
match node {
|
||||||
Node::Strong => {}
|
|
||||||
Node::Emph => {}
|
|
||||||
Node::Space => {}
|
|
||||||
Node::Linebreak => {}
|
|
||||||
Node::Parbreak => {}
|
|
||||||
Node::Text(_) => {}
|
Node::Text(_) => {}
|
||||||
Node::Heading(n) => v.visit_tree(&n.contents),
|
Node::Space => {}
|
||||||
|
Node::Strong(_) => {}
|
||||||
|
Node::Linebreak(_) => {}
|
||||||
|
Node::Parbreak(_) => {}
|
||||||
|
Node::Emph(_) => {}
|
||||||
|
Node::Heading(heading) => v.visit_tree(&heading.contents),
|
||||||
Node::Raw(_) => {}
|
Node::Raw(_) => {}
|
||||||
Node::Expr(expr) => v.visit_expr(expr),
|
Node::Expr(expr) => v.visit_expr(expr),
|
||||||
}
|
}
|
||||||
@ -64,7 +64,15 @@ visit! {
|
|||||||
|
|
||||||
fn visit_expr(v, node: &Expr) {
|
fn visit_expr(v, node: &Expr) {
|
||||||
match node {
|
match node {
|
||||||
Expr::Lit(_) => {}
|
Expr::None(_) => {}
|
||||||
|
Expr::Bool(_, _) => {}
|
||||||
|
Expr::Int(_, _) => {}
|
||||||
|
Expr::Float(_, _) => {}
|
||||||
|
Expr::Length(_, _, _) => {}
|
||||||
|
Expr::Angle(_, _, _) => {}
|
||||||
|
Expr::Percent(_, _) => {}
|
||||||
|
Expr::Color(_, _) => {}
|
||||||
|
Expr::Str(_, _) => {}
|
||||||
Expr::Ident(_) => {}
|
Expr::Ident(_) => {}
|
||||||
Expr::Array(e) => v.visit_array(e),
|
Expr::Array(e) => v.visit_array(e),
|
||||||
Expr::Dict(e) => v.visit_dict(e),
|
Expr::Dict(e) => v.visit_dict(e),
|
||||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
tests/ref/markup/parbreak.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.3 KiB |
@ -122,10 +122,7 @@
|
|||||||
#test((1, 2, 3) == (1, 2.0) + (3,), true)
|
#test((1, 2, 3) == (1, 2.0) + (3,), true)
|
||||||
#test((:) == (a: 1), false)
|
#test((:) == (a: 1), false)
|
||||||
#test((a: 2 - 1.0, b: 2) == (b: 2, a: 1), true)
|
#test((a: 2 - 1.0, b: 2) == (b: 2, a: 1), true)
|
||||||
#test([*Hi*] == [*Hi*], true)
|
|
||||||
|
|
||||||
#test("a" != "a", false)
|
#test("a" != "a", false)
|
||||||
#test([*] != [_], true)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test comparison operators.
|
// Test comparison operators.
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
---
|
---
|
||||||
// Test configuring paragraph properties.
|
// Test configuring paragraph properties.
|
||||||
|
|
||||||
#paragraph(spacing: 10pt, leading: 25%, word-spacing: 1pt)
|
#par(spacing: 10pt, leading: 25%, word-spacing: 1pt)
|
||||||
|
|
||||||
But, soft! what light through yonder window breaks? It is the east, and Juliet
|
But, soft! what light through yonder window breaks? It is the east, and Juliet
|
||||||
is the sun.
|
is the sun.
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test that it finishes an existing paragraph.
|
// Test that it finishes an existing paragraph.
|
||||||
Hello #paragraph(word-spacing: 0pt) t h e r e !
|
Hello #par(word-spacing: 0pt) t h e r e !
|
||||||
|
@ -10,5 +10,14 @@ Partly em_phas_ized.
|
|||||||
// Scoped to body.
|
// Scoped to body.
|
||||||
#rect[_Scoped] to body.
|
#rect[_Scoped] to body.
|
||||||
|
|
||||||
// Unterminated is fine.
|
---
|
||||||
_The End
|
#let emph = strong
|
||||||
|
_Strong_
|
||||||
|
|
||||||
|
#let emph() = "Hi"
|
||||||
|
_, _!
|
||||||
|
|
||||||
|
#let emph = "hi"
|
||||||
|
|
||||||
|
// Error: 1-2 expected function, found string
|
||||||
|
_
|
||||||
|
@ -42,3 +42,11 @@ No = heading
|
|||||||
|
|
||||||
// Escaped.
|
// Escaped.
|
||||||
\= No heading
|
\= No heading
|
||||||
|
|
||||||
|
---
|
||||||
|
// Make small, but double heading.
|
||||||
|
#let heading(contents) = heading(contents + contents, level: 6)
|
||||||
|
|
||||||
|
// The new heading's argument list doesn't contain `level`.
|
||||||
|
// Error: 1-11 unexpected argument
|
||||||
|
=== Twice.
|
||||||
|
@ -21,3 +21,11 @@ Trailing 2
|
|||||||
|
|
||||||
// Trailing before end of document.
|
// Trailing before end of document.
|
||||||
Trailing 3 \
|
Trailing 3 \
|
||||||
|
|
||||||
|
---
|
||||||
|
#let linebreak() = [
|
||||||
|
// Inside the old line break definition is still active.
|
||||||
|
#circle(radius: 2pt, fill: #000) \
|
||||||
|
]
|
||||||
|
|
||||||
|
A \ B \ C \
|
||||||
|
11
tests/typ/markup/parbreak.typ
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Test paragraph breaks.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Paragraph breaks don't exist!
|
||||||
|
#let parbreak() = [ ]
|
||||||
|
|
||||||
|
No more
|
||||||
|
|
||||||
|
paragraph breaks
|
||||||
|
|
||||||
|
for you!
|
@ -45,6 +45,14 @@ def hi():
|
|||||||
print("Hi!")
|
print("Hi!")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
// Make everything block-level.
|
||||||
|
#let raw(text) = raw(text, block: true)
|
||||||
|
|
||||||
|
// The new raw's argument list doesn't contain `block`.
|
||||||
|
// Error: 6-10 unexpected argument
|
||||||
|
This `is` block-level.
|
||||||
|
|
||||||
---
|
---
|
||||||
// Unterminated.
|
// Unterminated.
|
||||||
// Error: 2:1 expected backtick(s)
|
// Error: 2:1 expected backtick(s)
|
||||||
|
@ -10,5 +10,13 @@ Partly str*ength*ened.
|
|||||||
// Scoped to body.
|
// Scoped to body.
|
||||||
#rect[*Scoped] to body.
|
#rect[*Scoped] to body.
|
||||||
|
|
||||||
// Unterminated is fine.
|
---
|
||||||
*The End
|
#let strong = emph
|
||||||
|
*Emph*
|
||||||
|
|
||||||
|
#let strong() = "Bye"
|
||||||
|
*, *!
|
||||||
|
|
||||||
|
#let strong = 123
|
||||||
|
// Error: 1-2 expected function, found integer
|
||||||
|
*
|
||||||
|
@ -209,7 +209,7 @@ fn test_part(
|
|||||||
let (local_compare_ref, ref_diags) = parse_metadata(src, &map);
|
let (local_compare_ref, ref_diags) = parse_metadata(src, &map);
|
||||||
let compare_ref = local_compare_ref.unwrap_or(compare_ref);
|
let compare_ref = local_compare_ref.unwrap_or(compare_ref);
|
||||||
|
|
||||||
let mut scope = library::new();
|
let mut scope = library::_new();
|
||||||
|
|
||||||
let panics = Rc::new(RefCell::new(vec![]));
|
let panics = Rc::new(RefCell::new(vec![]));
|
||||||
register_helpers(&mut scope, Rc::clone(&panics));
|
register_helpers(&mut scope, Rc::clone(&panics));
|
||||||
|