Port remaining parser tests 🚚

This commit is contained in:
Laurenz 2021-01-16 15:08:03 +01:00
parent d763f0f5a6
commit 51efb0f4d6
16 changed files with 121 additions and 303 deletions

View File

@ -11,15 +11,15 @@ use crate::paper::{Paper, PaperClass, PAPER_A4};
/// The evaluation state.
#[derive(Debug, Clone, PartialEq)]
pub struct State {
/// The current page state.
/// The current page settings.
pub page: PageSettings,
/// The current paragraph state.
/// The current paragraph settings.
pub par: ParSettings,
/// The current font state.
/// The current font settings.
pub font: FontSettings,
/// The current directions.
/// The current layouting directions.
pub dirs: LayoutDirs,
/// The current alignments.
/// The current alignments of an item in its parent.
pub align: ChildAlign,
}

View File

@ -51,6 +51,7 @@ impl ContainsChar for FaceBuf {
}
}
/// Simplify font loader construction from an [`FsIndex`].
#[cfg(feature = "fs")]
pub trait FsIndexExt {
/// Create a font loader backed by a boxed [`FsSource`] which serves all

View File

@ -99,10 +99,12 @@ impl Length {
impl Display for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use LengthUnit::*;
// Format with the unit that yields the shortest output, preferring
// larger units when tied.
// larger / metrics units when tied.
let mut buf = ryu::Buffer::new();
let unit = [LengthUnit::Cm, LengthUnit::Mm, LengthUnit::Pt]
let unit = [Cm, Mm, In, Pt]
.iter()
.copied()
.min_by_key(|&unit| buf.format(self.to_unit(unit)).len())

View File

@ -412,6 +412,3 @@ fn expr_let(p: &mut Parser) -> Option<Expr> {
p.end_group();
pat.map(|pat| Expr::Let(ExprLet { pat, expr: rhs }))
}
#[cfg(test)]
mod tests;

View File

@ -1,291 +0,0 @@
#![allow(non_snake_case)]
use std::fmt::Debug;
use super::parse;
use crate::diag::{Diag, Level, Pass};
use crate::geom::LengthUnit;
use crate::syntax::*;
use BinOp::*;
use Expr::{Float, Int, Length};
use Node::{Space, Strong};
use UnOp::{Neg, Pos};
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, Template![@$($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) -> Node {
Node::Text(text.into())
}
fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> Node {
Node::Raw(NodeRaw {
lang: lang.map(|id| Ident(id.into())),
lines: lines.iter().map(ToString::to_string).collect(),
inline,
})
}
fn Id(ident: &str) -> Expr {
Expr::Ident(Ident(ident.to_string()))
}
fn Str(string: &str) -> Expr {
Expr::Str(string.to_string())
}
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()),
})
}
fn Group(expr: Expr) -> Expr {
Expr::Group(Box::new(expr))
}
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)*) => {
Node::Expr(Call!(@$($tts)*))
};
}
macro_rules! Args {
(@$a:expr) => {
Argument::Pos(into!($a))
};
(@$a:expr => $b:expr) => {
Argument::Named(Named {
name: into!($a).map(|s: &str| Ident(s.into())),
expr: into!($b)
})
};
($($a:expr $(=> $b:expr)?),* $(,)?) => {
vec![$(Args!(@$a $(=> $b)?)),*]
};
}
macro_rules! Array {
(@$($expr:expr),* $(,)?) => {
vec![$(into!($expr)),*]
};
($($tts:tt)*) => {
Expr::Array(Array![@$($tts)*])
};
}
macro_rules! Template {
(@$($node:expr),* $(,)?) => {
vec![$(into!($node)),*]
};
($($tts:tt)*) => {
Expr::Template(Template![@$($tts)*])
};
}
macro_rules! Block {
(@$expr:expr) => {
Expr::Block(Box::new($expr))
};
($expr:expr) => {
Node::Expr(Block!(@$expr))
};
}
#[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_groups() {
// Test paren group.
t!("{({1) + 3}"
nodes: [Block!(Binary(Group(Block!(@Int(1))), Add, Int(3)))],
errors: [S(4..4, "expected closing brace")]);
// 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![Template![Strong]])],
errors: [S(6..6, "expected closing bracket")]);
// Test brace group.
t!("{1 + [}"
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![], Template![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()}" Block!(Call!(@"f")));
t!("{[[f]]}" Block!(Template![Call!("f")]));
// Missing or bad value.
t!("{}{1u}"
nodes: [],
errors: [S(1..1, "expected expression"),
S(3..5, "expected expression, found invalid token")]);
// Too much stuff.
t!("{1 #{} end"
nodes: [Block!(Int(1)), Space, Text("end")],
errors: [S(3..4, "unexpected hex value"),
S(4..5, "unexpected opening brace")]);
}
#[test]
fn test_parse_expressions() {
// Parentheses.
t!("{(x)}{(1)}" Block!(Group(Id("x"))), Block!(Group(Int(1))));
// Unary operations.
t!("{+1}" Block!(Unary(Pos, Int(1))));
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, LengthUnit::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, Group(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")]);
}

View File

@ -441,7 +441,6 @@ impl Debug for Tokens<'_> {
#[allow(non_snake_case)]
mod tests {
use super::*;
use crate::parse::tests::check;
use Option::None;
use Token::{Ident, *};
@ -539,10 +538,23 @@ mod tests {
let src = $src;
let exp = vec![$($token),*];
let found = Tokens::new(&src, $mode).collect::<Vec<_>>();
check(&src, exp, found, false);
check(&src, exp, found);
}};
}
#[track_caller]
fn check<T>(src: &str, exp: T, found: T)
where
T: Debug + PartialEq,
{
if exp != found {
println!("source: {:?}", src);
println!("expected: {:#?}", exp);
println!("found: {:#?}", found);
panic!("test failed");
}
}
#[test]
fn test_tokenize_brackets() {
// Test in markup.

View File

@ -150,6 +150,7 @@ impl Span {
/// When set to `false` comparisons with `PartialEq` ignore spans.
#[cfg(test)]
#[allow(unused)]
pub(crate) fn set_cmp(cmp: bool) {
CMP_SPANS.with(|cell| cell.set(cmp));
}

BIN
tests/ref/lang/blocks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
tests/ref/lang/raw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

21
tests/typ/lang/blocks.typ Normal file
View File

@ -0,0 +1,21 @@
{1}
// Function calls.
{f(1)}
{[[f 1]]}
// Error: 1:2-1:2 expected expression
{}
// Error: 1:2-1:4 expected expression, found invalid token
{1u}
// Error: 1:5-1:5 expected closing brace
{({1) + 2}
// Error: 1:12-1:12 expected closing bracket
{[*] + [ok*}
// Error: 2:4-2:5 unexpected hex value
// Error: 1:5-1:6 unexpected opening brace
{1 #{} _end_

View File

@ -41,6 +41,10 @@
// Error: 1:6-1:6 expected function name
[f 1|]
// Error: 2:5-2:5 expected closing paren
// Error: 1:8-1:9 expected expression, found closing paren
[f (|f )]
// With actual functions.
[box width: 1cm | image "res/rhino.png"]
@ -65,6 +69,7 @@
[f (x):1]
---
// Ref: false
// Error: 2:2-2:3 a value of type string is not callable
#let x = "string";
[x]
@ -76,6 +81,16 @@
// Error: 3:1-3:1 expected closing bracket
[
---
// Ref: false
// Error: 2:2-2:3 expected function name, found closing paren
// Error: 3:1-3:1 expected closing bracket
[)
---
// Error: 3:1-3:1 expected closing bracket
[f [*]
---
// Error: 3:1-3:1 expected closing bracket
[f][`a]`

View File

@ -0,0 +1,37 @@
#let a = 2;
#let b = 4;
// Unary operations.
{+1}
{-1}
{--1}
// Binary operations.
{"a"+"b"}
{1-2}
{a * b}
{12pt/.4}
// Associativity.
{1+2+3}
{1/2*3}
// Precedence.
{1+2*-3}
// Parentheses.
{(a)}
{(2)}
{(1+2)*3}
// Confusion with floating-point literal.
{1e+2-1e-2}
// Error: 1:3-1:3 expected expression
{-}
// Error: 1:4-1:4 expected expression
{1+}
// Error: 1:4-1:4 expected expression
{2*}

23
tests/typ/lang/raw.typ Normal file
View File

@ -0,0 +1,23 @@
The keyword ``rust let``.
`#let x = 1`
`[f 1]`
---
[font 6pt]
``py
import this
def say_hi():
print("Hello World!")
``
---
````
```backticks```
````
---
// Error: 2:1-2:1 expected backtick(s)
`endless