mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Braced content -> Bracketed templates ✏
This commit is contained in:
parent
3c7d249ae4
commit
105cda0e69
@ -170,7 +170,7 @@ impl Eval for Spanned<&Expr> {
|
||||
Expr::Binary(v) => v.with_span(self.span).eval(ctx),
|
||||
Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)),
|
||||
Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)),
|
||||
Expr::Content(v) => Value::Content(v.clone()),
|
||||
Expr::Template(v) => Value::Template(v.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,7 +275,7 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
|
||||
(Str(a), Str(b)) => Str(a + &b),
|
||||
(Array(a), Array(b)) => Array(concat(a, b)),
|
||||
(Dict(a), Dict(b)) => Dict(concat(a, b)),
|
||||
(Content(a), Content(b)) => Content(concat(a, b)),
|
||||
(Template(a), Template(b)) => Template(concat(a, b)),
|
||||
|
||||
(a, b) => {
|
||||
ctx.diag(error!(
|
||||
|
@ -8,7 +8,7 @@ use super::{Args, Eval, EvalContext};
|
||||
use crate::color::Color;
|
||||
use crate::geom::{Angle, Length, Linear, Relative};
|
||||
use crate::pretty::{pretty, Pretty, Printer};
|
||||
use crate::syntax::{pretty_content_expr, Spanned, Tree, WithSpan};
|
||||
use crate::syntax::{pretty_template_expr, Spanned, Tree, WithSpan};
|
||||
|
||||
/// A computational value.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@ -37,8 +37,8 @@ pub enum Value {
|
||||
Array(ValueArray),
|
||||
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
||||
Dict(ValueDict),
|
||||
/// A content value: `{*Hi* there}`.
|
||||
Content(ValueContent),
|
||||
/// A template value: `[*Hi* there]`.
|
||||
Template(ValueTemplate),
|
||||
/// An executable function.
|
||||
Func(ValueFunc),
|
||||
/// Any object.
|
||||
@ -71,7 +71,7 @@ impl Value {
|
||||
Self::Str(_) => String::TYPE_NAME,
|
||||
Self::Array(_) => ValueArray::TYPE_NAME,
|
||||
Self::Dict(_) => ValueDict::TYPE_NAME,
|
||||
Self::Content(_) => ValueContent::TYPE_NAME,
|
||||
Self::Template(_) => ValueTemplate::TYPE_NAME,
|
||||
Self::Func(_) => ValueFunc::TYPE_NAME,
|
||||
Self::Any(v) => v.type_name(),
|
||||
Self::Error => "error",
|
||||
@ -87,7 +87,7 @@ impl Eval for &Value {
|
||||
ctx.push(ctx.make_text_node(match self {
|
||||
Value::None => return,
|
||||
Value::Str(s) => s.clone(),
|
||||
Value::Content(tree) => return tree.eval(ctx),
|
||||
Value::Template(tree) => return tree.eval(ctx),
|
||||
other => pretty(other),
|
||||
}));
|
||||
}
|
||||
@ -114,7 +114,7 @@ impl Pretty for Value {
|
||||
Value::Str(v) => write!(p, "{:?}", v).unwrap(),
|
||||
Value::Array(array) => array.pretty(p),
|
||||
Value::Dict(dict) => dict.pretty(p),
|
||||
Value::Content(content) => pretty_content_expr(content, p),
|
||||
Value::Template(template) => pretty_template_expr(template, p),
|
||||
Value::Func(v) => v.pretty(p),
|
||||
Value::Any(v) => v.pretty(p),
|
||||
Value::Error => p.push_str("(error)"),
|
||||
@ -155,8 +155,8 @@ impl Pretty for ValueDict {
|
||||
}
|
||||
}
|
||||
|
||||
/// A content value: `{*Hi* there}`.
|
||||
pub type ValueContent = Tree;
|
||||
/// A template value: `[*Hi* there]`.
|
||||
pub type ValueTemplate = Tree;
|
||||
|
||||
/// A wrapper around a reference-counted executable function.
|
||||
#[derive(Clone)]
|
||||
@ -407,28 +407,26 @@ macro_rules! impl_primitive {
|
||||
|
||||
impl_primitive! { bool: "boolean", Value::Bool }
|
||||
impl_primitive! { i64: "integer", Value::Int }
|
||||
impl_primitive! { Length: "length", Value::Length }
|
||||
impl_primitive! { Angle: "angle", Value::Angle }
|
||||
impl_primitive! { Relative: "relative", Value::Relative }
|
||||
impl_primitive! { Color: "color", Value::Color }
|
||||
impl_primitive! { String: "string", Value::Str }
|
||||
impl_primitive! { ValueArray: "array", Value::Array }
|
||||
impl_primitive! { ValueDict: "dictionary", Value::Dict }
|
||||
impl_primitive! { ValueContent: "content", Value::Content }
|
||||
impl_primitive! { ValueFunc: "function", Value::Func }
|
||||
|
||||
impl_primitive! {
|
||||
f64: "float",
|
||||
Value::Float,
|
||||
Value::Int(v) => v as f64,
|
||||
}
|
||||
|
||||
impl_primitive! { Length: "length", Value::Length }
|
||||
impl_primitive! { Angle: "angle", Value::Angle }
|
||||
impl_primitive! { Relative: "relative", Value::Relative }
|
||||
impl_primitive! {
|
||||
Linear: "linear",
|
||||
Value::Linear,
|
||||
Value::Length(v) => v.into(),
|
||||
Value::Relative(v) => v.into(),
|
||||
}
|
||||
impl_primitive! { Color: "color", Value::Color }
|
||||
impl_primitive! { String: "string", Value::Str }
|
||||
impl_primitive! { ValueArray: "array", Value::Array }
|
||||
impl_primitive! { ValueDict: "dictionary", Value::Dict }
|
||||
impl_primitive! { ValueTemplate: "template", Value::Template }
|
||||
impl_primitive! { ValueFunc: "function", Value::Func }
|
||||
|
||||
impl From<&str> for Value {
|
||||
fn from(v: &str) -> Self {
|
||||
@ -512,7 +510,7 @@ mod tests {
|
||||
test_pretty(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
|
||||
test_pretty(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
|
||||
test_pretty("hello", r#""hello""#);
|
||||
test_pretty(vec![Spanned::zero(Node::Strong)], "{*}");
|
||||
test_pretty(vec![Spanned::zero(Node::Strong)], "[*]");
|
||||
test_pretty(ValueFunc::new("nil", |_, _| Value::None), "(function nil)");
|
||||
test_pretty(ValueAny::new(1), "1");
|
||||
test_pretty(Value::Error, "(error)");
|
||||
@ -528,8 +526,8 @@ mod tests {
|
||||
// Dictionary.
|
||||
let mut dict = BTreeMap::new();
|
||||
dict.insert("one".into(), Value::Int(1));
|
||||
dict.insert("two".into(), Value::Content(parse("[f]").output));
|
||||
dict.insert("two".into(), Value::Template(parse("[f]").output));
|
||||
test_pretty(BTreeMap::new(), "(:)");
|
||||
test_pretty(dict, "(one: 1, two: [f])");
|
||||
test_pretty(dict, "(one: 1, two: [[f]])");
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ pub fn align(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
ctx.start_par_group();
|
||||
}
|
||||
|
||||
if let Some(body) = args.find::<ValueContent>(ctx) {
|
||||
if let Some(body) = args.find::<ValueTemplate>(ctx) {
|
||||
body.eval(ctx);
|
||||
ctx.state = snapshot;
|
||||
}
|
||||
@ -190,7 +190,7 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
|
||||
ctx.start_content_group();
|
||||
|
||||
if let Some(body) = args.find::<ValueContent>(ctx) {
|
||||
if let Some(body) = args.find::<ValueTemplate>(ctx) {
|
||||
body.eval(ctx);
|
||||
}
|
||||
|
||||
@ -317,7 +317,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
ctx.set_dirs(Gen::new(main, cross));
|
||||
|
||||
let mut softness = ctx.end_page_group(|_| false);
|
||||
if let Some(body) = args.find::<ValueContent>(ctx) {
|
||||
if let Some(body) = args.find::<ValueTemplate>(ctx) {
|
||||
// TODO: Restrict body to a single page?
|
||||
ctx.start_page_group(Softness::Hard);
|
||||
body.eval(ctx);
|
||||
|
@ -95,7 +95,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(body) = args.find::<ValueContent>(ctx) {
|
||||
if let Some(body) = args.find::<ValueTemplate>(ctx) {
|
||||
body.eval(ctx);
|
||||
ctx.state = snapshot;
|
||||
}
|
||||
|
@ -49,32 +49,20 @@ fn tree(p: &mut Parser) -> Tree {
|
||||
/// Parse a syntax node.
|
||||
fn node(p: &mut Parser, at_start: bool) -> Option<Node> {
|
||||
let node = match p.peek()? {
|
||||
Token::Text(text) => Node::Text(text.into()),
|
||||
Token::Space(newlines) => {
|
||||
if newlines < 2 {
|
||||
Node::Space
|
||||
} else {
|
||||
Node::Parbreak
|
||||
}
|
||||
}
|
||||
|
||||
Token::LineComment(_) | Token::BlockComment(_) => {
|
||||
p.eat();
|
||||
return None;
|
||||
}
|
||||
|
||||
// Bracket call.
|
||||
Token::LeftBracket => {
|
||||
return Some(Node::Expr(Expr::Call(bracket_call(p))));
|
||||
}
|
||||
|
||||
// Code block.
|
||||
Token::LeftBrace => {
|
||||
return Some(Node::Expr(block_expr(p)?));
|
||||
return Some(Node::Expr(code_block(p)?));
|
||||
}
|
||||
|
||||
// Markup.
|
||||
Token::Star => Node::Strong,
|
||||
Token::Underscore => Node::Emph,
|
||||
Token::Tilde => Node::Text("\u{00A0}".into()),
|
||||
Token::Backslash => Node::Linebreak,
|
||||
Token::Hash => {
|
||||
if at_start {
|
||||
return Some(Node::Heading(heading(p)));
|
||||
@ -82,9 +70,24 @@ fn node(p: &mut Parser, at_start: bool) -> Option<Node> {
|
||||
Node::Text(p.get(p.peek_span()).into())
|
||||
}
|
||||
}
|
||||
Token::Backslash => Node::Linebreak,
|
||||
Token::Space(newlines) => {
|
||||
if newlines < 2 {
|
||||
Node::Space
|
||||
} else {
|
||||
Node::Parbreak
|
||||
}
|
||||
}
|
||||
Token::Text(text) => Node::Text(text.into()),
|
||||
Token::Raw(t) => Node::Raw(raw(p, t)),
|
||||
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
||||
|
||||
// Comments.
|
||||
Token::LineComment(_) | Token::BlockComment(_) => {
|
||||
p.eat();
|
||||
return None;
|
||||
}
|
||||
|
||||
_ => {
|
||||
p.diag_unexpected();
|
||||
return None;
|
||||
@ -150,27 +153,6 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String {
|
||||
text
|
||||
}
|
||||
|
||||
/// Parse a block expression.
|
||||
fn block_expr(p: &mut Parser) -> Option<Expr> {
|
||||
p.push_mode(TokenMode::Code);
|
||||
p.start_group(Group::Brace);
|
||||
let expr = expr(p);
|
||||
while !p.eof() {
|
||||
p.diag_unexpected();
|
||||
}
|
||||
p.pop_mode();
|
||||
p.end_group();
|
||||
expr
|
||||
}
|
||||
|
||||
/// Parse a parenthesized function call.
|
||||
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> ExprCall {
|
||||
p.start_group(Group::Paren);
|
||||
let args = p.span(arguments);
|
||||
p.end_group();
|
||||
ExprCall { name, args }
|
||||
}
|
||||
|
||||
/// Parse a bracketed function call.
|
||||
fn bracket_call(p: &mut Parser) -> ExprCall {
|
||||
p.push_mode(TokenMode::Code);
|
||||
@ -189,7 +171,7 @@ fn bracket_call(p: &mut Parser) -> ExprCall {
|
||||
p.end_group();
|
||||
|
||||
if p.peek() == Some(Token::LeftBracket) {
|
||||
let body = p.span(|p| Expr::Content(bracket_body(p)));
|
||||
let body = p.span(|p| Expr::Template(bracket_body(p)));
|
||||
inner.span.expand(body.span);
|
||||
inner.v.args.v.push(Argument::Pos(body));
|
||||
}
|
||||
@ -197,7 +179,7 @@ fn bracket_call(p: &mut Parser) -> ExprCall {
|
||||
while let Some(mut top) = outer.pop() {
|
||||
let span = inner.span;
|
||||
let node = inner.map(|c| Node::Expr(Expr::Call(c)));
|
||||
let expr = Expr::Content(vec![node]).with_span(span);
|
||||
let expr = Expr::Template(vec![node]).with_span(span);
|
||||
top.v.args.v.push(Argument::Pos(expr));
|
||||
inner = top;
|
||||
}
|
||||
@ -236,6 +218,19 @@ fn bracket_body(p: &mut Parser) -> Tree {
|
||||
tree
|
||||
}
|
||||
|
||||
/// Parse a code block: `{...}`.
|
||||
fn code_block(p: &mut Parser) -> Option<Expr> {
|
||||
p.push_mode(TokenMode::Code);
|
||||
p.start_group(Group::Brace);
|
||||
let expr = expr(p);
|
||||
while !p.eof() {
|
||||
p.diag_unexpected();
|
||||
}
|
||||
p.pop_mode();
|
||||
p.end_group();
|
||||
expr
|
||||
}
|
||||
|
||||
/// Parse an expression: `term (+ term)*`.
|
||||
fn expr(p: &mut Parser) -> Option<Expr> {
|
||||
binops(p, term, |token| match token {
|
||||
@ -297,15 +292,14 @@ fn factor(p: &mut Parser) -> Option<Expr> {
|
||||
/// Parse a value.
|
||||
fn value(p: &mut Parser) -> Option<Expr> {
|
||||
let expr = match p.peek() {
|
||||
// Bracketed function call.
|
||||
// Template.
|
||||
Some(Token::LeftBracket) => {
|
||||
let node = p.span(|p| Node::Expr(Expr::Call(bracket_call(p))));
|
||||
return Some(Expr::Content(vec![node]));
|
||||
return Some(Expr::Template(template(p)));
|
||||
}
|
||||
|
||||
// Content expression.
|
||||
// Nested block.
|
||||
Some(Token::LeftBrace) => {
|
||||
return Some(Expr::Content(content(p)));
|
||||
return code_block(p);
|
||||
}
|
||||
|
||||
// Dictionary or just a parenthesized expression.
|
||||
@ -346,16 +340,24 @@ fn value(p: &mut Parser) -> Option<Expr> {
|
||||
Some(expr)
|
||||
}
|
||||
|
||||
// Parse a content value: `{...}`.
|
||||
fn content(p: &mut Parser) -> Tree {
|
||||
// Parse a template value: `[...]`.
|
||||
fn template(p: &mut Parser) -> Tree {
|
||||
p.push_mode(TokenMode::Markup);
|
||||
p.start_group(Group::Brace);
|
||||
p.start_group(Group::Bracket);
|
||||
let tree = tree(p);
|
||||
p.pop_mode();
|
||||
p.end_group();
|
||||
tree
|
||||
}
|
||||
|
||||
/// Parse a parenthesized function call.
|
||||
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> ExprCall {
|
||||
p.start_group(Group::Paren);
|
||||
let args = p.span(arguments);
|
||||
p.end_group();
|
||||
ExprCall { name, args }
|
||||
}
|
||||
|
||||
/// Parse an identifier.
|
||||
fn ident(p: &mut Parser) -> Option<Ident> {
|
||||
p.eat_map(|token| match token {
|
||||
|
@ -26,7 +26,7 @@ macro_rules! t {
|
||||
$(spans = $spans;)?
|
||||
|
||||
let Pass { output, feedback } = parse($src);
|
||||
check($src, Content![@$($node),*], output, spans);
|
||||
check($src, Template![@$($node),*], output, spans);
|
||||
check(
|
||||
$src,
|
||||
vec![
|
||||
@ -158,9 +158,9 @@ macro_rules! Args {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! Content {
|
||||
macro_rules! Template {
|
||||
(@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]);
|
||||
($($tts:tt)*) => (Expr::Content(Content![@$($tts)*]));
|
||||
($($tts:tt)*) => (Expr::Template(Template![@$($tts)*]));
|
||||
}
|
||||
|
||||
macro_rules! Call {
|
||||
@ -227,30 +227,30 @@ fn test_parse_simple_nodes() {
|
||||
fn test_parse_headings() {
|
||||
// Basics with spans.
|
||||
t!("# a"
|
||||
nodes: [S(0..3, Heading(S(0..1, 0), Content![
|
||||
nodes: [S(0..3, Heading(S(0..1, 0), Template![
|
||||
@S(1..2, Space), S(2..3, Text("a"))
|
||||
]))],
|
||||
spans: true);
|
||||
|
||||
// Multiple hashtags.
|
||||
t!("### three" Heading(2, Content![@Space, Text("three")]));
|
||||
t!("###### six" Heading(5, Content![@Space, Text("six")]));
|
||||
t!("### three" Heading(2, Template![@Space, Text("three")]));
|
||||
t!("###### six" Heading(5, Template![@Space, Text("six")]));
|
||||
|
||||
// Start of heading.
|
||||
t!("/**/#" Heading(0, Content![@]));
|
||||
t!("[f][# ok]" Call!("f", Args![Content![Heading(0, Content![
|
||||
t!("/**/#" Heading(0, Template![@]));
|
||||
t!("[f][# ok]" Call!("f", Args![Template![Heading(0, Template![
|
||||
@Space, Text("ok")
|
||||
])]]));
|
||||
|
||||
// End of heading.
|
||||
t!("# a\nb" Heading(0, Content![@Space, Text("a")]), Space, Text("b"));
|
||||
t!("# a\nb" Heading(0, Template![@Space, Text("a")]), Space, Text("b"));
|
||||
|
||||
// Continued heading.
|
||||
t!("# a{\n1\n}b" Heading(0, Content![
|
||||
t!("# a{\n1\n}b" Heading(0, Template![
|
||||
@Space, Text("a"), Block(Int(1)), Text("b")
|
||||
]));
|
||||
t!("# a[f][\n\n]d" Heading(0, Content![@
|
||||
Space, Text("a"), Call!("f", Args![Content![Parbreak]]), Text("d"),
|
||||
t!("# a[f][\n\n]d" Heading(0, Template![@
|
||||
Space, Text("a"), Call!("f", Args![Template![Parbreak]]), Text("d"),
|
||||
]));
|
||||
|
||||
// No heading.
|
||||
@ -260,7 +260,7 @@ fn test_parse_headings() {
|
||||
|
||||
// Too many hashtags.
|
||||
t!("####### seven"
|
||||
nodes: [Heading(5, Content![@Space, Text("seven")])],
|
||||
nodes: [Heading(5, Template![@Space, Text("seven")])],
|
||||
warnings: [S(0..7, "section depth should not exceed 6")]);
|
||||
}
|
||||
|
||||
@ -293,13 +293,9 @@ fn test_parse_escape_sequences() {
|
||||
#[test]
|
||||
fn test_parse_groups() {
|
||||
// Test paren group.
|
||||
t!("{([v 1) + 3}"
|
||||
nodes: [Block(Binary(
|
||||
Content![Call!("v", Args![Int(1)])],
|
||||
Add,
|
||||
Int(3),
|
||||
))],
|
||||
errors: [S(6..6, "expected closing bracket")]);
|
||||
t!("{({1) + 3}"
|
||||
nodes: [Block(Binary(Int(1), Add, Int(3)))],
|
||||
errors: [S(4..4, "expected closing brace")]);
|
||||
|
||||
// Test bracket group.
|
||||
t!("[)"
|
||||
@ -307,19 +303,18 @@ fn test_parse_groups() {
|
||||
errors: [S(1..2, "expected function name, found closing paren"),
|
||||
S(2..2, "expected closing bracket")]);
|
||||
|
||||
t!("[v {*]_"
|
||||
nodes: [Call!("v", Args![Content![Strong]]), Emph],
|
||||
errors: [S(5..5, "expected closing brace")]);
|
||||
t!("[v [*]"
|
||||
nodes: [Call!("v", Args![Template![Strong]])],
|
||||
errors: [S(6..6, "expected closing bracket")]);
|
||||
|
||||
// Test brace group.
|
||||
t!("{1 + [}"
|
||||
nodes: [Block(Binary(Int(1), Add, Content![Call!("")]))],
|
||||
errors: [S(6..6, "expected function name"),
|
||||
S(6..6, "expected closing bracket")]);
|
||||
nodes: [Block(Binary(Int(1), Add, Template![]))],
|
||||
errors: [S(6..6, "expected closing bracket")]);
|
||||
|
||||
// Test subheader group.
|
||||
t!("[v (|u )]"
|
||||
nodes: [Call!("v", Args![Array![], Content![Call!("u")]])],
|
||||
nodes: [Call!("v", Args![Array![], Template![Call!("u")]])],
|
||||
errors: [S(4..4, "expected closing paren"),
|
||||
S(7..8, "expected expression, found closing paren")]);
|
||||
}
|
||||
@ -331,7 +326,7 @@ fn test_parse_blocks() {
|
||||
|
||||
// Function calls.
|
||||
t!("{f()}" Call!("f"));
|
||||
t!("{[f]}" Block(Content![Call!("f")]));
|
||||
t!("{[[f]]}" Block(Template![Call!("f")]));
|
||||
|
||||
// Missing or bad value.
|
||||
t!("{}{1u}"
|
||||
@ -353,15 +348,15 @@ fn test_parse_bracket_funcs() {
|
||||
t!("[ v ]" Call!("v"));
|
||||
|
||||
// Body and no body.
|
||||
t!("[v][[f]]" Call!("v", Args![Content![Call!("f")]]));
|
||||
t!("[v][v][v]" Call!("v", Args![Content![Text("v")]]), Call!("v"));
|
||||
t!("[v][[f]]" Call!("v", Args![Template![Call!("f")]]));
|
||||
t!("[v][v][v]" Call!("v", Args![Template![Text("v")]]), Call!("v"));
|
||||
t!("[v] [f]" Call!("v"), Space, Call!("f"));
|
||||
|
||||
// Spans.
|
||||
t!("[v 1][📐]"
|
||||
nodes: [S(0..11, Call!(S(1..2, "v"), S(3..4, Args![
|
||||
S(3..4, Int(1)),
|
||||
S(5..11, Content![S(6..10, Text("📐"))]),
|
||||
S(5..11, Template![S(6..10, Text("📐"))]),
|
||||
])))],
|
||||
spans: true);
|
||||
|
||||
@ -389,35 +384,35 @@ fn test_parse_bracket_funcs() {
|
||||
|
||||
// Raw in body eats closing bracket.
|
||||
t!("[v][`a]`"
|
||||
nodes: [Call!("v", Args![Content![Raw(None, &["a]"], true)]])],
|
||||
nodes: [Call!("v", Args![Template![Raw(None, &["a]"], true)]])],
|
||||
errors: [S(8..8, "expected closing bracket")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_chaining() {
|
||||
// Basic.
|
||||
t!("[a | b]" Call!("a", Args![Content![Call!("b")]]));
|
||||
t!("[a|b|c]" Call!("a", Args![Content![
|
||||
Call!("b", Args![Content![Call!("c")]])
|
||||
t!("[a | b]" Call!("a", Args![Template![Call!("b")]]));
|
||||
t!("[a|b|c]" Call!("a", Args![Template![
|
||||
Call!("b", Args![Template![Call!("c")]])
|
||||
]]));
|
||||
|
||||
// With body and spans.
|
||||
t!("[a|b][💕]"
|
||||
nodes: [S(0..11, Call!(S(1..2, "a"), S(2..2, Args![
|
||||
S(3..11, Content![S(3..11, Call!(S(3..4, "b"), S(4..4, Args![
|
||||
S(5..11, Content![S(6..10, Text("💕"))])
|
||||
S(3..11, Template![S(3..11, Call!(S(3..4, "b"), S(4..4, Args![
|
||||
S(5..11, Template![S(6..10, Text("💕"))])
|
||||
])))])
|
||||
])))],
|
||||
spans: true);
|
||||
|
||||
// No name in second subheader.
|
||||
t!("[a 1|]"
|
||||
nodes: [Call!("a", Args![Int(1), Content![Call!("")]])],
|
||||
nodes: [Call!("a", Args![Int(1), Template![Call!("")]])],
|
||||
errors: [S(5..5, "expected function name")]);
|
||||
|
||||
// No name in first subheader.
|
||||
t!("[|a true]"
|
||||
nodes: [Call!("", Args![Content![Call!("a", Args![Bool(true)])]])],
|
||||
nodes: [Call!("", Args![Template![Call!("a", Args![Bool(true)])]])],
|
||||
errors: [S(1..1, "expected function name")]);
|
||||
}
|
||||
|
||||
@ -534,12 +529,12 @@ fn test_parse_dictionaries() {
|
||||
errors: [S(8..9, "expected named pair, found expression")]);
|
||||
|
||||
// Dictionary marker followed by more stuff.
|
||||
t!("{(:1 b:2, true::)}"
|
||||
nodes: [Block(Dict!["b" => Int(2)])],
|
||||
t!("{(:1 b:[], true::)}"
|
||||
nodes: [Block(Dict!["b" => Template![]])],
|
||||
errors: [S(3..4, "expected named pair, found expression"),
|
||||
S(4..4, "expected comma"),
|
||||
S(10..14, "name must be identifier"),
|
||||
S(15..16, "expected expression, found colon")]);
|
||||
S(11..15, "name must be identifier"),
|
||||
S(16..17, "expected expression, found colon")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -615,7 +610,11 @@ fn test_parse_values() {
|
||||
errors: [S(1..4, "invalid color")]);
|
||||
|
||||
// Content.
|
||||
t!("{{*Hi*}}" Block(Content![Strong, Text("Hi"), Strong]));
|
||||
t!("{[*Hi*]}" Block(Template![Strong, Text("Hi"), Strong]));
|
||||
|
||||
// Nested blocks.
|
||||
t!("{{1}}" Block(Int(1)));
|
||||
t!("{{{1+2}}}" Block(Binary(Int(1), Add, Int(2))));
|
||||
|
||||
// Invalid tokens.
|
||||
t!("{1u}"
|
||||
|
@ -562,7 +562,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenize_body_symbols() {
|
||||
fn test_tokenize_markup_symbols() {
|
||||
// Test markup tokens.
|
||||
t!(Markup[" a1"]: "*" => Star);
|
||||
t!(Markup: "_" => Underscore);
|
||||
@ -572,7 +572,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenize_header_symbols() {
|
||||
fn test_tokenize_code_symbols() {
|
||||
// Test all symbols.
|
||||
t!(Code: "," => Comma);
|
||||
t!(Code: ":" => Colon);
|
||||
@ -653,6 +653,27 @@ mod tests {
|
||||
t!(Code[" /"]: "falser" => Ident("falser"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenize_whitespace() {
|
||||
// Test basic whitespace.
|
||||
t!(Both["a1/"]: "" => );
|
||||
t!(Both["a1/"]: " " => Space(0));
|
||||
t!(Both["a1/"]: " " => Space(0));
|
||||
t!(Both["a1/"]: "\t" => Space(0));
|
||||
t!(Both["a1/"]: " \t" => Space(0));
|
||||
t!(Both["a1/"]: "\u{202F}" => Space(0));
|
||||
|
||||
// Test newline counting.
|
||||
t!(Both["a1/"]: "\n" => Space(1));
|
||||
t!(Both["a1/"]: "\n " => Space(1));
|
||||
t!(Both["a1/"]: " \n" => Space(1));
|
||||
t!(Both["a1/"]: " \n " => Space(1));
|
||||
t!(Both["a1/"]: "\r\n" => Space(1));
|
||||
t!(Both["a1/"]: " \n\t \n " => Space(2));
|
||||
t!(Both["a1/"]: "\n\r" => Space(2));
|
||||
t!(Both["a1/"]: " \r\r\n \x0D" => Space(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenize_text() {
|
||||
// Test basic text.
|
||||
@ -738,27 +759,6 @@ mod tests {
|
||||
t!(Markup: r"\u{1🏕}" => UnicodeEscape("1", false), Text("🏕"), RightBrace);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenize_whitespace() {
|
||||
// Test basic whitespace.
|
||||
t!(Both["a1/"]: "" => );
|
||||
t!(Both["a1/"]: " " => Space(0));
|
||||
t!(Both["a1/"]: " " => Space(0));
|
||||
t!(Both["a1/"]: "\t" => Space(0));
|
||||
t!(Both["a1/"]: " \t" => Space(0));
|
||||
t!(Both["a1/"]: "\u{202F}" => Space(0));
|
||||
|
||||
// Test newline counting.
|
||||
t!(Both["a1/"]: "\n" => Space(1));
|
||||
t!(Both["a1/"]: "\n " => Space(1));
|
||||
t!(Both["a1/"]: " \n" => Space(1));
|
||||
t!(Both["a1/"]: " \n " => Space(1));
|
||||
t!(Both["a1/"]: "\r\n" => Space(1));
|
||||
t!(Both["a1/"]: " \n\t \n " => Space(2));
|
||||
t!(Both["a1/"]: "\n\r" => Space(2));
|
||||
t!(Both["a1/"]: " \r\r\n \x0D" => Space(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenize_idents() {
|
||||
// Test valid identifiers.
|
||||
|
@ -3,8 +3,8 @@
|
||||
pub use crate::diag::{Feedback, Pass};
|
||||
#[doc(no_inline)]
|
||||
pub use crate::eval::{
|
||||
Args, CastResult, Eval, EvalContext, Value, ValueAny, ValueArray, ValueContent,
|
||||
ValueDict,
|
||||
Args, CastResult, Eval, EvalContext, Value, ValueAny, ValueArray, ValueDict,
|
||||
ValueTemplate,
|
||||
};
|
||||
pub use crate::geom::*;
|
||||
#[doc(no_inline)]
|
||||
|
@ -38,8 +38,8 @@ pub enum Expr {
|
||||
Array(ExprArray),
|
||||
/// A dictionary expression: `(color: #f79143, pattern: dashed)`.
|
||||
Dict(ExprDict),
|
||||
/// A content expression: `{*Hello* there!}`.
|
||||
Content(ExprContent),
|
||||
/// A template expression: `[*Hi* there!]`.
|
||||
Template(ExprTemplate),
|
||||
}
|
||||
|
||||
impl Pretty for Expr {
|
||||
@ -60,24 +60,16 @@ impl Pretty for Expr {
|
||||
Self::Binary(binary) => binary.pretty(p),
|
||||
Self::Array(array) => array.pretty(p),
|
||||
Self::Dict(dict) => dict.pretty(p),
|
||||
Self::Content(content) => pretty_content_expr(content, p),
|
||||
Self::Template(template) => pretty_template_expr(template, p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pretty print content in an expression context.
|
||||
pub fn pretty_content_expr(tree: &Tree, p: &mut Printer) {
|
||||
if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = tree.as_slice() {
|
||||
// Remove unncessary braces from content expression containing just a
|
||||
// single function call.
|
||||
//
|
||||
// Example: Transforms "{(call: {[f]})}" => "{(call: [f])}"
|
||||
pretty_bracket_call(call, p, false);
|
||||
} else {
|
||||
p.push_str("{");
|
||||
tree.pretty(p);
|
||||
p.push_str("}");
|
||||
}
|
||||
/// Pretty print a template in an expression context.
|
||||
pub fn pretty_template_expr(tree: &Tree, p: &mut Printer) {
|
||||
p.push_str("[");
|
||||
tree.pretty(p);
|
||||
p.push_str("]");
|
||||
}
|
||||
|
||||
/// An invocation of a function: `[foo ...]`, `foo(...)`.
|
||||
@ -111,8 +103,8 @@ pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) {
|
||||
|
||||
// Find out whether this can be written with a body or as a chain.
|
||||
//
|
||||
// Example: Transforms "[v {Hi}]" => "[v][Hi]".
|
||||
if let [head @ .., Argument::Pos(Spanned { v: Expr::Content(content), .. })] =
|
||||
// Example: Transforms "[v [Hi]]" => "[v][Hi]".
|
||||
if let [head @ .., Argument::Pos(Spanned { v: Expr::Template(template), .. })] =
|
||||
call.args.v.as_slice()
|
||||
{
|
||||
// Previous arguments.
|
||||
@ -124,11 +116,11 @@ pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) {
|
||||
// Find out whether this can written as a chain.
|
||||
//
|
||||
// Example: Transforms "[v][[f]]" => "[v | f]".
|
||||
if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = content.as_slice() {
|
||||
if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() {
|
||||
return pretty_bracket_call(call, p, true);
|
||||
} else {
|
||||
p.push_str("][");
|
||||
content.pretty(p);
|
||||
template.pretty(p);
|
||||
}
|
||||
} else if !call.args.v.is_empty() {
|
||||
p.push_str(" ");
|
||||
@ -291,8 +283,8 @@ impl Pretty for ExprDict {
|
||||
}
|
||||
}
|
||||
|
||||
/// A content expression: `{*Hello* there!}`.
|
||||
pub type ExprContent = Tree;
|
||||
/// A template expression: `[*Hi* there!]`.
|
||||
pub type ExprTemplate = Tree;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@ -301,8 +293,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_pretty_print_chaining() {
|
||||
// All equivalent.
|
||||
test_pretty("[v [f]]", "[v | f]");
|
||||
test_pretty("[v {[f]}]", "[v | f]");
|
||||
test_pretty("[v [[f]]]", "[v | f]");
|
||||
test_pretty("[v][[f]]", "[v | f]");
|
||||
test_pretty("[v | f]", "[v | f]");
|
||||
}
|
||||
@ -321,9 +312,8 @@ mod tests {
|
||||
test_pretty("{(:)}", "{(:)}");
|
||||
test_pretty("{(percent: 5%)}", "{(percent: 5%)}");
|
||||
|
||||
// Content expression without unncessary braces.
|
||||
test_pretty("[v [f], 1]", "[v [f], 1]");
|
||||
test_pretty("(func: {[f]})", "(func: [f])");
|
||||
// Content expression.
|
||||
test_pretty("[v [[f]], 1]", "[v [[f]], 1]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -3,18 +3,18 @@ use super::*;
|
||||
/// A syntax node, encompassing a single logical entity of parsed source code.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Node {
|
||||
/// Plain text.
|
||||
Text(String),
|
||||
/// 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,
|
||||
/// Strong text was enabled / disabled.
|
||||
Strong,
|
||||
/// Emphasized text was enabled / disabled.
|
||||
Emph,
|
||||
/// Plain text.
|
||||
Text(String),
|
||||
/// A section heading.
|
||||
Heading(NodeHeading),
|
||||
/// An optionally syntax-highlighted raw block.
|
||||
@ -26,12 +26,12 @@ pub enum Node {
|
||||
impl Pretty for Node {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
match self {
|
||||
Self::Text(text) => p.push_str(&text),
|
||||
Self::Strong => p.push_str("*"),
|
||||
Self::Emph => p.push_str("_"),
|
||||
Self::Space => p.push_str(" "),
|
||||
Self::Linebreak => p.push_str(r"\"),
|
||||
Self::Parbreak => p.push_str("\n\n"),
|
||||
Self::Strong => p.push_str("*"),
|
||||
Self::Emph => p.push_str("_"),
|
||||
Self::Text(text) => p.push_str(&text),
|
||||
Self::Heading(heading) => heading.pretty(p),
|
||||
Self::Raw(raw) => raw.pretty(p),
|
||||
Self::Expr(expr) => pretty_expr_node(expr, p),
|
||||
@ -50,8 +50,8 @@ pub fn pretty_expr_node(expr: &Expr, p: &mut Printer) {
|
||||
|
||||
// Remove unncessary nesting of content and expression blocks.
|
||||
//
|
||||
// Example: Transforms "{{Hi}}" => "Hi".
|
||||
Expr::Content(content) => content.pretty(p),
|
||||
// Example: Transforms "{[Hi]}" => "Hi".
|
||||
Expr::Template(template) => template.pretty(p),
|
||||
|
||||
_ => {
|
||||
p.push_str("{");
|
||||
@ -179,9 +179,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_removes_nesting() {
|
||||
// Even levels of nesting do not matter.
|
||||
test_pretty("{{Hi}}", "Hi");
|
||||
test_pretty("{{{{Hi}}}}", "Hi");
|
||||
// Nesting does not matter.
|
||||
test_pretty("{{x}}", "{x}");
|
||||
test_pretty("{{{x}}}", "{x}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -189,8 +189,7 @@ mod tests {
|
||||
// All reduces to a simple bracket call.
|
||||
test_pretty("{v()}", "[v]");
|
||||
test_pretty("[v]", "[v]");
|
||||
test_pretty("{[v]}", "[v]");
|
||||
test_pretty("{{[v]}}", "[v]");
|
||||
test_pretty("{[[v]]}", "[v]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
Loading…
x
Reference in New Issue
Block a user