typst/src/parse/tests.rs
2021-01-01 16:43:58 +01:00

565 lines
15 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![allow(non_snake_case)]
use std::fmt::Debug;
use super::parse;
use crate::color::RgbaColor;
use crate::diag::{Diag, Level, Pass};
use crate::eval::DictKey;
use crate::geom::Unit;
use crate::syntax::*;
use BinOp::*;
use SynNode::{Emph, Linebreak, Parbreak, Space, Strong};
use UnOp::*;
macro_rules! t {
($src:literal
nodes: [$($node:expr),* $(,)?]
$(, errors: [$($err:expr),* $(,)?])?
$(, warnings: [$($warn:expr),* $(,)?])?
$(, spans: $spans:expr)?
$(,)?
) => {{
#[allow(unused)]
let mut spans = false;
$(spans = $spans;)?
let Pass { output, feedback } = parse($src);
check($src, Content![@$($node),*], output, spans);
check(
$src,
vec![
$($(into!($err).map(|s: &str| Diag::new(Level::Error, s)),)*)?
$($(into!($warn).map(|s: &str| Diag::new(Level::Warning, s)),)*)?
],
feedback.diags,
true,
);
}};
($src:literal $($node:expr),* $(,)?) => {
t!($src nodes: [$($node),*]);
};
}
/// Assert that expected and found are equal, printing both and the source of
/// the test case if they aren't.
///
/// When `cmp_spans` is false, spans are ignored.
#[track_caller]
pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool)
where
T: Debug + PartialEq,
{
Span::set_cmp(cmp_spans);
if exp != found {
println!("source: {:?}", src);
println!("expected: {:#?}", exp);
println!("found: {:#?}", found);
panic!("test failed");
}
Span::set_cmp(true);
}
/// Shorthand for `Spanned::new`.
fn S<T>(span: impl Into<Span>, v: T) -> Spanned<T> {
Spanned::new(v, span)
}
// Enables tests to optionally specify spans.
impl<T> From<T> for Spanned<T> {
fn from(t: T) -> Self {
Spanned::zero(t)
}
}
/// Shorthand for `Into::<Spanned<_>>::into`.
macro_rules! into {
($val:expr) => {
Into::<Spanned<_>>::into($val)
};
}
fn Text(text: &str) -> SynNode {
SynNode::Text(text.into())
}
fn Heading(level: impl Into<Spanned<u8>>, contents: SynTree) -> SynNode {
SynNode::Heading(NodeHeading { level: level.into(), contents })
}
fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> SynNode {
SynNode::Raw(NodeRaw {
lang: lang.map(|id| Ident(id.into())),
lines: lines.iter().map(ToString::to_string).collect(),
inline,
})
}
fn Id(ident: &str) -> Expr {
Expr::Lit(Lit::Ident(Ident(ident.to_string())))
}
fn Bool(b: bool) -> Expr {
Expr::Lit(Lit::Bool(b))
}
fn Int(int: i64) -> Expr {
Expr::Lit(Lit::Int(int))
}
fn Float(float: f64) -> Expr {
Expr::Lit(Lit::Float(float))
}
fn Percent(percent: f64) -> Expr {
Expr::Lit(Lit::Percent(percent))
}
fn Length(val: f64, unit: Unit) -> Expr {
Expr::Lit(Lit::Length(val, unit))
}
fn Color(color: RgbaColor) -> Expr {
Expr::Lit(Lit::Color(color))
}
fn Str(string: &str) -> Expr {
Expr::Lit(Lit::Str(string.to_string()))
}
fn Block(expr: Expr) -> SynNode {
SynNode::Expr(expr)
}
fn Binary(
lhs: impl Into<Spanned<Expr>>,
op: impl Into<Spanned<BinOp>>,
rhs: impl Into<Spanned<Expr>>,
) -> Expr {
Expr::Binary(ExprBinary {
lhs: Box::new(lhs.into()),
op: op.into(),
rhs: Box::new(rhs.into()),
})
}
fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr {
Expr::Unary(ExprUnary {
op: op.into(),
expr: Box::new(expr.into()),
})
}
macro_rules! Dict {
(@$($a:expr $(=> $b:expr)?),* $(,)?) => {
LitDict(vec![$(#[allow(unused)] {
let key: Option<Spanned<DictKey>> = None;
let expr = $a;
$(
let key = Some(into!($a).map(|s: &str| s.into()));
let expr = $b;
)?
LitDictEntry { key, expr: into!(expr) }
}),*])
};
($($tts:tt)*) => (Expr::Lit(Lit::Dict(Dict![@$($tts)*])));
}
macro_rules! Content {
(@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]);
($($tts:tt)*) => (Expr::Lit(Lit::Content(Content![@$($tts)*])));
}
macro_rules! Call {
(@@$name:expr) => {
Call!(@@$name, Args![])
};
(@@$name:expr, $args:expr) => {
ExprCall {
name: into!($name).map(|s: &str| Ident(s.into())),
args: into!($args),
}
};
(@$($tts:tt)*) => (Expr::Call(Call!(@@$($tts)*)));
($($tts:tt)*) => (SynNode::Expr(Call!(@$($tts)*)));
}
macro_rules! Args {
($($tts:tt)*) => (Dict![@$($tts)*]);
}
#[test]
fn test_parse_comments() {
// In body.
t!("a// you\nb" Text("a"), Space, Text("b"));
t!("* // \n /*\n\n*/*" Strong, Space, Space, Strong);
// In header.
t!("[v /*12pt*/]" Call!("v"));
t!("[v //\n]" Call!("v"));
t!("[v 12, /*\n*/ size: 14]" Call!("v", Args![Int(12), "size" => Int(14)]));
// Error.
t!("a*/b"
nodes: [Text("a"), Text("b")],
errors: [S(1..3, "unexpected end of block comment")]);
}
#[test]
fn test_parse_simple_nodes() {
// Basics.
t!("");
t!(" " Space);
t!("hi" Text("hi"));
t!("🧽" Text("🧽"));
t!("_" Emph);
t!("*" Strong);
t!("~" Text("\u{00A0}"));
t!(r"\" Linebreak);
t!("\n\n" Parbreak);
// Multiple nodes.
t!("ab c" Text("ab"), Space, Text("c"));
t!("a`hi`\r\n\r*" Text("a"), Raw(None, &["hi"], true), Parbreak, Strong);
// Spans.
t!("*🌍*"
nodes: [S(0..1, Strong), S(1..5, Text("🌍")), S(5..6, Strong)],
spans: true);
// Errors.
t!("]}"
nodes: [],
errors: [S(0..1, "unexpected closing bracket"),
S(1..2, "unexpected closing brace")]);
}
#[test]
fn test_parse_headings() {
// Basics with spans.
t!("#a"
nodes: [S(0..2, Heading(S(0..1, 0), Content![@S(1..2, Text("a"))]))],
spans: true);
// Multiple hashtags.
t!("###three" Heading(2, Content![@Text("three")]));
t!("###### six" Heading(5, Content![@Space, Text("six")]));
// Start of heading.
t!("/**/#" Heading(0, Content![@]));
t!("[f][#ok]" Call!("f", Args![Content![Heading(0, Content![@Text("ok")])]]));
// End of heading.
t!("#a\nb" Heading(0, Content![@Text("a")]), Space, Text("b"));
// Continued heading.
t!("#a{\n1\n}b" Heading(0, Content![@Text("a"), Block(Int(1)), Text("b")]));
t!("#a[f][\n\n]d" Heading(0, Content![@
Text("a"), Call!("f", Args![Content![Parbreak]]), Text("d"),
]));
// No heading.
t!(r"\#" Text("#"));
t!("Nr. #1" Text("Nr."), Space, Text("#"), Text("1"));
t!("[v]#" Call!("v"), Text("#"));
// Too many hashtags.
t!("####### seven"
nodes: [Heading(5, Content![@Space, Text("seven")])],
warnings: [S(0..7, "section depth should not exceed 6")]);
}
#[test]
fn test_parse_raw() {
// Basic, mostly tested in tokenizer and resolver.
t!("`py`" nodes: [S(0..4, Raw(None, &["py"], true))], spans: true);
t!("`endless"
nodes: [Raw(None, &["endless"], true)],
errors: [S(8..8, "expected backtick(s)")]);
}
#[test]
fn test_parse_escape_sequences() {
// Basic, mostly tested in tokenizer.
t!(r"\[" Text("["));
t!(r"\u{1F3D5}" nodes: [S(0..9, Text("🏕"))], spans: true);
// Bad value.
t!(r"\u{FFFFFF}"
nodes: [Text(r"\u{FFFFFF}")],
errors: [S(0..10, "invalid unicode escape sequence")]);
// No closing brace.
t!(r"\u{41*"
nodes: [Text("A"), Strong],
errors: [S(5..5, "expected closing brace")]);
}
#[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")]);
// Test bracket group.
t!("[)"
nodes: [Call!("")],
errors: [S(1..2, "expected function name, found closing paren"),
S(2..2, "expected closing bracket")]);
t!("[v {]}"
nodes: [Call!("v", Args![Content![]])],
errors: [S(4..4, "expected closing brace"),
S(5..6, "unexpected closing brace")]);
// 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")]);
// Test subheader group.
t!("[v (|u )]"
nodes: [Call!("v", Args![Dict![], Content![Call!("u")]])],
errors: [S(4..4, "expected closing paren"),
S(7..8, "expected expression, found closing paren")]);
}
#[test]
fn test_parse_blocks() {
// Basic with spans.
t!("{1}" nodes: [S(0..3, Block(Int(1)))], spans: true);
// Function calls.
t!("{f()}" Call!("f"));
t!("{[f]}" Block(Content![Call!("f")]));
// Missing or bad value.
t!("{}{1u}"
nodes: [],
errors: [S(1..1, "expected expression"),
S(3..5, "expected expression, found invalid token")]);
}
#[test]
fn test_parse_bracket_funcs() {
// Basic.
t!("[function]" Call!("function"));
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"), 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("📐"))]),
])))],
spans: true);
// No name and no closing bracket.
t!("["
nodes: [Call!("")],
errors: [S(1..1, "expected function name"),
S(1..1, "expected closing bracket")]);
// No name.
t!("[]"
nodes: [Call!("")],
errors: [S(1..1, "expected function name")]);
// Bad name.
t!("[# 1]"
nodes: [Call!("", Args![Int(1)])],
errors: [S(1..2, "expected function name, found hex value")]);
// String header eats closing bracket.
t!(r#"[v "]"#
nodes: [Call!("v", Args![Str("]")])],
errors: [S(5..5, "expected quote"),
S(5..5, "expected closing bracket")]);
// Raw in body eats closing bracket.
t!("[v][`a]`"
nodes: [Call!("v", Args![Content![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")]])
]]));
// 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("💕"))])
])))])
])))],
spans: true);
// No name in second subheader.
t!("[a 1|]"
nodes: [Call!("a", Args![Int(1), Content![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)])]])],
errors: [S(1..1, "expected function name")]);
}
#[test]
fn test_parse_arguments() {
// Bracket functions.
t!("[v 1]" Call!("v", Args![Int(1)]));
t!("[v 1,]" Call!("v", Args![Int(1)]));
t!("[v a]" Call!("v", Args![Id("a")]));
t!("[v a,]" Call!("v", Args![Id("a")]));
t!("[v a:2]" Call!("v", Args!["a" => Int(2)]));
// Parenthesized function with nested dictionary literal.
t!(r#"{f(1, a: (2, 3), #004, b: "five")}"# Block(Call!(@"f", Args![
Int(1),
"a" => Dict![Int(2), Int(3)],
Color(RgbaColor::new(0, 0, 0x44, 0xff)),
"b" => Str("five"),
])));
// Bad expression.
t!("[v */]"
nodes: [Call!("v", Args![])],
errors: [S(3..5, "expected expression, found end of block comment")]);
// Missing comma between arguments.
t!("[v 1 2]"
nodes: [Call!("v", Args![Int(1), Int(2)])],
errors: [S(4..4, "expected comma")]);
// Missing expression after name.
t!("[v a:]"
nodes: [Call!("v", Args![])],
errors: [S(5..5, "expected expression")]);
// Bad expression after name.
t!("[v a:1:]"
nodes: [Call!("v", Args!["a" => Int(1)])],
errors: [S(6..7, "expected expression, found colon")]);
// Name has to be identifier. Number parsed as positional argument.
t!("[v 1:]"
nodes: [Call!("v", Args![Int(1)])],
errors: [S(4..5, "expected expression, found colon")]);
// Parsed as two positional arguments.
t!("[v 1:2]"
nodes: [Call!("v", Args![Int(1), Int(2)])],
errors: [S(4..5, "expected expression, found colon"),
S(4..4, "expected comma")]);
}
#[test]
fn test_parse_dict_literals() {
// Basic.
t!("{()}" Block(Dict![]));
// With spans.
t!("{(1, two: 2)}"
nodes: [S(0..13, Block(Dict![
S(2..3, Int(1)),
S(5..8, "two") => S(10..11, Int(2)),
]))],
spans: true);
// Unclosed.
t!("{(}"
nodes: [Block(Dict![])],
errors: [S(2..2, "expected closing paren")]);
}
#[test]
fn test_parse_expressions() {
// Parenthesis.
t!("{(x)}" Block(Id("x")));
// Unary operations.
t!("{-1}" Block(Unary(Neg, Int(1))));
t!("{--1}" Block(Unary(Neg, Unary(Neg, Int(1)))));
// Binary operations.
t!(r#"{"x"+"y"}"# Block(Binary(Str("x"), Add, Str("y"))));
t!("{1-2}" Block(Binary(Int(1), Sub, Int(2))));
t!("{a * b}" Block(Binary(Id("a"), Mul, Id("b"))));
t!("{12pt/.4}" Block(Binary(Length(12.0, Unit::Pt), Div, Float(0.4))));
// Associativity.
t!("{1+2+3}" Block(Binary(Binary(Int(1), Add, Int(2)), Add, Int(3))));
t!("{1/2*3}" Block(Binary(Binary(Int(1), Div, Int(2)), Mul, Int(3))));
// Precedence.
t!("{1+2*-3}" Block(Binary(
Int(1), Add, Binary(Int(2), Mul, Unary(Neg, Int(3))),
)));
// Confusion with floating-point literal.
t!("{1e-3-4e+4}" Block(Binary(Float(1e-3), Sub, Float(4e+4))));
// Spans + parentheses winning over precedence.
t!("{(1+2)*3}"
nodes: [S(0..9, Block(Binary(
S(1..6, Binary(S(2..3, Int(1)), S(3..4, Add), S(4..5, Int(2)))),
S(6..7, Mul),
S(7..8, Int(3)),
)))],
spans: true);
// Errors.
t!("{-}{1+}{2*}"
nodes: [Block(Int(1)), Block(Int(2))],
errors: [S(2..2, "expected expression"),
S(6..6, "expected expression"),
S(10..10, "expected expression")]);
}
#[test]
fn test_parse_values() {
// Basics.
t!("{_}" Block(Id("_")));
t!("{name}" Block(Id("name")));
t!("{ke-bab}" Block(Id("ke-bab")));
t!("{α}" Block(Id("α")));
t!("{true}" Block(Bool(true)));
t!("{false}" Block(Bool(false)));
t!("{1.0e-4}" Block(Float(1e-4)));
t!("{3.15}" Block(Float(3.15)));
t!("{50%}" Block(Percent(50.0)));
t!("{4.5cm}" Block(Length(4.5, Unit::Cm)));
t!("{12e1pt}" Block(Length(12e1, Unit::Pt)));
// Strings.
t!(r#"{"hi"}"# Block(Str("hi")));
t!(r#"{"a\n[]\"\u{1F680}string"}"# Block(Str("a\n[]\"🚀string")));
// Colors.
t!("{#f7a20500}" Block(Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0))));
t!("{#a5}"
nodes: [Block(Color(RgbaColor::new(0, 0, 0, 0xff)))],
errors: [S(1..4, "invalid color")]);
}