diff --git a/src/eval/state.rs b/src/eval/state.rs index a88dfd070..21fb7fb6c 100644 --- a/src/eval/state.rs +++ b/src/eval/state.rs @@ -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, } diff --git a/src/font.rs b/src/font.rs index 01ea48c00..e57183316 100644 --- a/src/font.rs +++ b/src/font.rs @@ -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 diff --git a/src/geom/length.rs b/src/geom/length.rs index 00803e13c..1a45c63c1 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -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()) diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 5fdaa4ce0..b6836d384 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -412,6 +412,3 @@ fn expr_let(p: &mut Parser) -> Option { p.end_group(); pat.map(|pat| Expr::Let(ExprLet { pat, expr: rhs })) } - -#[cfg(test)] -mod tests; diff --git a/src/parse/tests.rs b/src/parse/tests.rs deleted file mode 100644 index 9a35f5522..000000000 --- a/src/parse/tests.rs +++ /dev/null @@ -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(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(span: impl Into, v: T) -> Spanned { - Spanned::new(v, span) -} - -// Enables tests to optionally specify spans. -impl From for Spanned { - fn from(t: T) -> Self { - Spanned::zero(t) - } -} - -/// Shorthand for `Into::>::into`. -macro_rules! into { - ($val:expr) => { - Into::>::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>, - op: impl Into>, - rhs: impl Into>, -) -> Expr { - Expr::Binary(ExprBinary { - lhs: Box::new(lhs.into()), - op: op.into(), - rhs: Box::new(rhs.into()), - }) -} - -fn Unary(op: impl Into>, expr: impl Into>) -> 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")]); -} diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 85fc49780..68b31a876 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -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::>(); - check(&src, exp, found, false); + check(&src, exp, found); }}; } + #[track_caller] + fn check(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. diff --git a/src/syntax/span.rs b/src/syntax/span.rs index e924b03b1..fbde35afa 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -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)); } diff --git a/tests/ref/lang/blocks.png b/tests/ref/lang/blocks.png new file mode 100644 index 000000000..ca826c1b9 Binary files /dev/null and b/tests/ref/lang/blocks.png differ diff --git a/tests/ref/lang/bracket-call.png b/tests/ref/lang/bracket-call.png index e7ba46e31..16afb1879 100644 Binary files a/tests/ref/lang/bracket-call.png and b/tests/ref/lang/bracket-call.png differ diff --git a/tests/ref/lang/expressions.png b/tests/ref/lang/expressions.png new file mode 100644 index 000000000..309c32f1a Binary files /dev/null and b/tests/ref/lang/expressions.png differ diff --git a/tests/ref/lang/raw.png b/tests/ref/lang/raw.png new file mode 100644 index 000000000..f88194c0b Binary files /dev/null and b/tests/ref/lang/raw.png differ diff --git a/tests/ref/lang/values.png b/tests/ref/lang/values.png index 4205221b7..762ad64e3 100644 Binary files a/tests/ref/lang/values.png and b/tests/ref/lang/values.png differ diff --git a/tests/typ/lang/blocks.typ b/tests/typ/lang/blocks.typ new file mode 100644 index 000000000..cadd30dd1 --- /dev/null +++ b/tests/typ/lang/blocks.typ @@ -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_ diff --git a/tests/typ/lang/bracket-call.typ b/tests/typ/lang/bracket-call.typ index 642d6426a..79667e610 100644 --- a/tests/typ/lang/bracket-call.typ +++ b/tests/typ/lang/bracket-call.typ @@ -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]` diff --git a/tests/typ/lang/expressions.typ b/tests/typ/lang/expressions.typ new file mode 100644 index 000000000..017252892 --- /dev/null +++ b/tests/typ/lang/expressions.typ @@ -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*} diff --git a/tests/typ/lang/raw.typ b/tests/typ/lang/raw.typ new file mode 100644 index 000000000..22eda139a --- /dev/null +++ b/tests/typ/lang/raw.typ @@ -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