Port remaining parser tests 🚚
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
|
@ -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")]);
|
||||
}
|
@ -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.
|
||||
|
@ -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
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
tests/ref/lang/expressions.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
tests/ref/lang/raw.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 8.3 KiB |
21
tests/typ/lang/blocks.typ
Normal 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_
|
@ -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]`
|
||||
|
37
tests/typ/lang/expressions.typ
Normal 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
@ -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
|