summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-01-16 17:51:04 +0100
committerLaurenz <laurmaedje@gmail.com>2020-01-16 17:51:04 +0100
commit08b91a265fcda74f5463473938ec33873b49a7f7 (patch)
tree747ac6a0b385a14a4aa5adbc3f21ef7b9653bd78 /tests
parent15ad30555bdad8e7b192fdcf7d4543c0d3fb18ce (diff)
Powerful parser testing 🐱‍👤
Diffstat (limited to 'tests')
-rw-r--r--tests/layouter/coma.typ (renamed from tests/layouts/coma.typ)0
-rw-r--r--tests/layouter/stack.typ (renamed from tests/layouts/stack.typ)0
-rw-r--r--tests/parse.rs236
-rw-r--r--tests/parser/tokens.rs (renamed from tests/parsing/tokens.rs)8
-rw-r--r--tests/parser/trees.rs33
-rw-r--r--tests/parsing/trees.rs20
-rw-r--r--tests/src/layouter.rs (renamed from tests/layout.rs)9
-rw-r--r--tests/src/parser.rs311
-rw-r--r--tests/src/render.py (renamed from tests/render.py)14
-rw-r--r--tests/src/spanless.rs62
10 files changed, 423 insertions, 270 deletions
diff --git a/tests/layouts/coma.typ b/tests/layouter/coma.typ
index 14f639b5..14f639b5 100644
--- a/tests/layouts/coma.typ
+++ b/tests/layouter/coma.typ
diff --git a/tests/layouts/stack.typ b/tests/layouter/stack.typ
index cbca41dc..cbca41dc 100644
--- a/tests/layouts/stack.typ
+++ b/tests/layouter/stack.typ
diff --git a/tests/parse.rs b/tests/parse.rs
deleted file mode 100644
index 616f4d70..00000000
--- a/tests/parse.rs
+++ /dev/null
@@ -1,236 +0,0 @@
-#![allow(unused_imports)]
-#![allow(dead_code)]
-#![allow(non_snake_case)]
-
-use typstc::func::Scope;
-use typstc::size::Size;
-use typstc::syntax::*;
-use typstc::{function, parse};
-
-
-mod token_shorthands {
- pub use super::Token::{
- Whitespace as W,
- LineComment as LC, BlockComment as BC, StarSlash as SS,
- LeftBracket as LB, RightBracket as RB,
- LeftParen as LP, RightParen as RP,
- LeftBrace as LBR, RightBrace as RBR,
- Colon as CL, Comma as CM, Equals as EQ,
- ExprIdent as ID, ExprStr as STR, ExprSize as SIZE,
- ExprNumber as NUM, ExprBool as BOOL,
- Star as ST, Underscore as U, Backtick as B, Text as T,
- };
-}
-
-mod node_shorthands {
- use super::Node;
- pub use Node::{
- Space as S, Newline as N, Text,
- ToggleItalic as I, ToggleBolder as B, ToggleMonospace as M,
- Func,
- };
- pub fn T(text: &str) -> Node { Node::Text(text.to_string()) }
-}
-
-macro_rules! F {
- (@body None) => (None);
- (@body Some([$($tts:tt)*])) => ({
- let nodes = vec![$($tts)*].into_iter()
- .map(|v| Spanned { v, span: Span::ZERO })
- .collect();
-
- Some(SyntaxTree { nodes })
- });
-
- ($($body:tt)*) => ({
- Func(FuncCall(Box::new(DebugFn {
- pos: vec![],
- key: vec![],
- body: F!(@body $($body)*),
- })))
- });
-}
-
-function! {
- #[derive(Debug, PartialEq)]
- pub struct DebugFn {
- pos: Vec<Spanned<Expression>>,
- key: Vec<Pair>,
- body: Option<SyntaxTree>,
- }
-
- parse(args, body, ctx) {
- DebugFn {
- pos: args.iter_pos().collect(),
- key: args.iter_keys().collect(),
- body: parse!(optional: body, ctx),
- }
- }
-
- layout() { vec![] }
-}
-
-impl DebugFn {
- fn compare(&self, other: &DebugFn) -> bool {
- self.pos.iter().zip(&other.pos).all(|(a, b)| a.v == b.v)
- && self.key.iter().zip(&other.key)
- .all(|(a, b)| a.key.v == b.key.v && a.value.v == b.value.v)
- && match (&self.body, &other.body) {
- (Some(a), Some(b)) => compare(a, b),
- (None, None) => true,
- _ => false,
- }
- }
-}
-
-fn downcast(func: &FuncCall) -> &DebugFn {
- func.0.downcast::<DebugFn>().expect("not a debug fn")
-}
-
-fn compare(a: &SyntaxTree, b: &SyntaxTree) -> bool {
- for (x, y) in a.nodes.iter().zip(&b.nodes) {
- use node_shorthands::*;
- let same = match (&x.v, &y.v) {
- (S, S) | (N, N) | (I, I) | (B, B) | (M, M) => true,
- (Text(t1), Text(t2)) => t1 == t2,
- (Func(f1), Func(f2)) => {
- downcast(f1).compare(downcast(f2))
- }
- _ => false,
- };
-
- if !same { return false; }
- }
- true
-}
-
-/// Parses the test syntax.
-macro_rules! tokens {
- ($($task:ident $src:expr =>($line:expr)=> [$($tts:tt)*])*) => ({
- #[allow(unused_mut)]
- let mut cases = Vec::new();
- $(cases.push(($line, $src, tokens!(@$task [$($tts)*])));)*
- cases
- });
-
- (@t [$($tts:tt)*]) => ({
- use token_shorthands::*;
- Target::Tokenize(vec![$($tts)*])
- });
-
- (@ts [$($tts:tt)*]) => ({
- use token_shorthands::*;
- Target::TokenizeSpanned(tokens!(@__spans [$($tts)*]))
- });
-
- (@p [$($tts:tt)*]) => ({
- use node_shorthands::*;
-
- let nodes = vec![$($tts)*].into_iter()
- .map(|v| Spanned { v, span: Span::ZERO })
- .collect();
-
- Target::Parse(SyntaxTree { nodes })
- });
-
- (@ps [$($tts:tt)*]) => ({
- use node_shorthands::*;
- Target::ParseSpanned(tokens!(@__spans [$($tts)*]))
- });
-
- (@__spans [$(($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)),* $(,)?]) => ({
- vec![
- $(Spanned { v: $v, span: Span {
- start: Position { line: $sl, column: $sc },
- end: Position { line: $el, column: $ec },
- }}),*
- ]
- });
-}
-
-#[derive(Debug)]
-enum Target {
- Tokenize(Vec<Token<'static>>),
- TokenizeSpanned(Vec<Spanned<Token<'static>>>),
- Parse(SyntaxTree),
- ParseSpanned(SyntaxTree),
-}
-
-fn main() {
- let tests = include!("cache/parse");
- let mut errors = false;
-
- let len = tests.len();
- println!();
- println!("Running {} test{}", len, if len > 1 { "s" } else { "" });
-
- // Go through all test files.
- for (file, cases) in tests.into_iter() {
- print!("Testing: {}. ", file);
-
- let mut okay = 0;
- let mut failed = 0;
-
- // Go through all tests in a test file.
- for (line, src, target) in cases.into_iter() {
- let (correct, expected, found) = test_case(src, target);
-
- // Check whether the tokenization works correctly.
- if correct {
- okay += 1;
- } else {
- if failed == 0 {
- println!();
- }
-
- println!(" - Case failed in file {}.rs in line {}.", file, line);
- println!(" - Source: {:?}", src);
- println!(" - Expected: {:?}", expected);
- println!(" - Found: {:?}", found);
- println!();
-
- failed += 1;
- errors = true;
- }
- }
-
- // Print a small summary.
- print!("{} okay, {} failed.", okay, failed);
- if failed == 0 {
- print!(" ✔")
- }
- println!();
- }
-
- println!();
-
- if errors {
- std::process::exit(-1);
- }
-}
-
-fn test_case(src: &str, target: Target) -> (bool, String, String) {
- match target {
- Target::Tokenize(tokens) => {
- let found: Vec<_> = tokenize(src).map(Spanned::value).collect();
- (found == tokens, format!("{:?}", tokens), format!("{:?}", found))
- }
-
- Target::TokenizeSpanned(tokens) => {
- let found: Vec<_> = tokenize(src).collect();
- (found == tokens, format!("{:?}", tokens), format!("{:?}", found))
- }
-
- Target::Parse(tree) => {
- let scope = Scope::with_debug::<DebugFn>();
- let (found, _, errs) = parse(src, ParseContext { scope: &scope });
- (compare(&tree, &found), format!("{:?}", tree), format!("{:?}", found))
- }
-
- Target::ParseSpanned(tree) => {
- let scope = Scope::with_debug::<DebugFn>();
- let (found, _, _) = parse(src, ParseContext { scope: &scope });
- (tree == found, format!("{:?}", tree), format!("{:?}", found))
- }
- }
-}
diff --git a/tests/parsing/tokens.rs b/tests/parser/tokens.rs
index 14f4e521..fb48b32e 100644
--- a/tests/parsing/tokens.rs
+++ b/tests/parser/tokens.rs
@@ -41,13 +41,13 @@ t "[a: true, x=1]" => [LB, ID("a"), CL, W(0), BOOL(true), CM, W(0),
t "[120%]" => [LB, NUM(1.2), RB]
// Body only tokens.
-t "_*`" => [U, ST, B]
-t "[func]*bold*" => [LB, ID("func"), RB, ST, T("bold"), ST]
+t "_*`" => [U, S, B]
+t "[func]*bold*" => [LB, ID("func"), RB, S, T("bold"), S]
t "[_*`]" => [LB, T("_"), T("*"), T("`"), RB]
t "hi_you_ there" => [T("hi"), U, T("you"), U, W(0), T("there")]
// Nested functions.
-t "[f: [=][*]]" => [LB, ID("f"), CL, W(0), LB, EQ, RB, LB, ST, RB, RB]
+t "[f: [=][*]]" => [LB, ID("f"), CL, W(0), LB, EQ, RB, LB, S, RB, RB]
t "[_][[,],]," => [LB, T("_"), RB, LB, LB, CM, RB, T(","), RB, T(",")]
t "[=][=][=]" => [LB, EQ, RB, LB, T("="), RB, LB, EQ, RB]
t "[=][[=][=][=]]" => [LB, EQ, RB, LB, LB, EQ, RB, LB, T("="), RB, LB, EQ, RB, RB]
@@ -75,6 +75,6 @@ ts "[a=10]" => [(0:0, 0:1, LB), (0:1, 0:2, ID("a")), (0:2, 0:3, EQ),
(0:3, 0:5, NUM(10.0)), (0:5, 0:6, RB)]
ts r#"[x = "(1)"]*"# => [(0:0, 0:1, LB), (0:1, 0:2, ID("x")), (0:2, 0:3, W(0)),
(0:3, 0:4, EQ), (0:4, 0:5, W(0)), (0:5, 0:10, STR("(1)")),
- (0:10, 0:11, RB), (0:11, 0:12, ST)]
+ (0:10, 0:11, RB), (0:11, 0:12, S)]
ts "// ab\r\n\nf" => [(0:0, 0:5, LC(" ab")), (0:5, 2:0, W(2)), (2:0, 2:1, T("f"))]
ts "/*b*/_" => [(0:0, 0:5, BC("b")), (0:5, 0:6, U)]
diff --git a/tests/parser/trees.rs b/tests/parser/trees.rs
new file mode 100644
index 00000000..442f71dd
--- /dev/null
+++ b/tests/parser/trees.rs
@@ -0,0 +1,33 @@
+p "" => []
+p "hi" => [T("hi")]
+p "hi you" => [T("hi"), S, T("you")]
+p "❤\n\n 🌍" => [T("❤"), N, T("🌍")]
+
+p "[func]" => [func!("func"; None)]
+p "[tree][hi *you*]" => [func!("tree"; Some([T("hi"), S, B, T("you"), B]))]
+
+p "from [align: left] to" => [
+ T("from"), S, func!("align", pos: [ID("left")]; None), S, T("to"),
+]
+
+p "[box: x=1.2pt, false][a b c] bye" => [
+ func!(
+ "box",
+ pos: [BOOL(false)],
+ key: ["x" => SIZE(Size::pt(1.2))];
+ Some([T("a"), S, T("b"), S, T("c")])
+ ),
+ S, T("bye"),
+]
+
+c "hi" => []
+c "[align: left][\n _body_\n]" => [
+ (0:0, 0:1, B),
+ (0:1, 0:6, FN),
+ (0:6, 0:7, CL),
+ (0:8, 0:12, ID),
+ (0:12, 0:13, B),
+ (0:13, 0:14, B),
+ (1:4, 1:10, IT),
+ (2:0, 2:2, B),
+]
diff --git a/tests/parsing/trees.rs b/tests/parsing/trees.rs
deleted file mode 100644
index 78b16828..00000000
--- a/tests/parsing/trees.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-p "" => []
-p "hi" => [T("hi")]
-p "hi you" => [T("hi"), S, T("you")]
-p "❤\n\n 🌍" => [T("❤"), N, T("🌍")]
-p "[func]" => [F!(None)]
-p "[tree][hi *you*]" => [F!(Some([T("hi"), S, B, T("you"), B]))]
-// p "from [align: left] to" => [
-// T("from"), S,
-// F!("align", pos=[ID("left")], None),
-// S, T("to"),
-// ]
-// p "[box: x=1.2pt, false][a b c] bye" => [
-// F!(
-// "box",
-// pos=[BOOL(false)],
-// key=["x": SIZE(Size::pt(1.2))],
-// Some([T("a"), S, T("b"), S, T("c")]),
-// ),
-// S, T("bye"),
-// ]
diff --git a/tests/layout.rs b/tests/src/layouter.rs
index 007b3c3f..6d38666b 100644
--- a/tests/layout.rs
+++ b/tests/src/layouter.rs
@@ -15,16 +15,17 @@ use typstc::style::PageStyle;
use typstc::toddle::query::FileSystemFontProvider;
use typstc::export::pdf::PdfExporter;
-type Result<T> = std::result::Result<T, Box<dyn Error>>;
-fn main() -> Result<()> {
+type DynResult<T> = Result<T, Box<dyn Error>>;
+
+fn main() -> DynResult<()> {
let opts = Options::parse();
create_dir_all("tests/cache/serial")?;
create_dir_all("tests/cache/render")?;
create_dir_all("tests/cache/pdf")?;
- let tests: Vec<_> = read_dir("tests/layouts/")?.collect();
+ let tests: Vec<_> = read_dir("tests/layouter/")?.collect();
let mut filtered = Vec::new();
for entry in tests {
@@ -62,7 +63,7 @@ fn main() -> Result<()> {
}
/// Create a _PDF_ with a name from the source code.
-fn test(name: &str, src: &str) -> Result<()> {
+fn test(name: &str, src: &str) -> DynResult<()> {
println!("Testing: {}.", name);
let mut typesetter = Typesetter::new();
diff --git a/tests/src/parser.rs b/tests/src/parser.rs
new file mode 100644
index 00000000..ecf1544c
--- /dev/null
+++ b/tests/src/parser.rs
@@ -0,0 +1,311 @@
+use std::fmt::Debug;
+
+use typstc::func::Scope;
+use typstc::size::Size;
+use typstc::syntax::*;
+use typstc::{function, parse};
+
+mod spanless;
+use spanless::SpanlessEq;
+
+
+/// The result of a single test case.
+enum Case {
+ Okay,
+ Failed {
+ line: usize,
+ src: &'static str,
+ expected: String,
+ found: String,
+ }
+}
+
+/// Test all tests.
+fn test(tests: Vec<(&str, Vec<Case>)>) {
+ println!();
+
+ let mut errors = false;
+
+ let len = tests.len();
+ println!("Running {} test{}", len, if len > 1 { "s" } else { "" });
+
+ for (file, cases) in tests {
+ print!("Testing: {}. ", file);
+
+ let mut okay = 0;
+ let mut failed = 0;
+
+ for case in cases {
+ match case {
+ Case::Okay => okay += 1,
+ Case::Failed { line, src, expected, found } => {
+ println!();
+ println!(" - Case failed in file {}.rs in line {}.", file, line);
+ println!(" - Source: {:?}", src);
+ println!(" - Expected: {}", expected);
+ println!(" - Found: {}", found);
+
+ failed += 1;
+ }
+ }
+ }
+
+ // Print a small summary.
+ print!("{} okay, {} failed.", okay, failed);
+ if failed == 0 {
+ print!(" ✔")
+ } else {
+ errors = true;
+ }
+
+ println!();
+ }
+
+ println!();
+
+ if errors {
+ std::process::exit(-1);
+ }
+}
+
+/// The main test macro.
+macro_rules! tokens {
+ ($($task:ident $src:expr =>($line:expr)=> [$($e:tt)*])*) => ({
+ vec![$({
+ let (okay, expected, found) = case!($task $src, [$($e)*]);
+ if okay {
+ Case::Okay
+ } else {
+ Case::Failed {
+ line: $line,
+ src: $src,
+ expected: format(expected),
+ found: format(found),
+ }
+ }
+ }),*]
+ });
+}
+
+//// Indented formatting for failed cases.
+fn format(thing: impl Debug) -> String {
+ format!("{:#?}", thing).replace('\n', "\n ")
+}
+
+/// Evaluates a single test.
+macro_rules! case {
+ (t $($rest:tt)*) => (case!(@tokenize SpanlessEq::spanless_eq, $($rest)*));
+ (ts $($rest:tt)*) => (case!(@tokenize PartialEq::eq, $($rest)*));
+
+ (@tokenize $cmp:expr, $src:expr, [$($e:tt)*]) => ({
+ let expected = list!(tokens [$($e)*]);
+ let found = tokenize($src).collect::<Vec<_>>();
+ ($cmp(&found, &expected), expected, found)
+ });
+
+ (p $($rest:tt)*) => (case!(@parse SpanlessEq::spanless_eq, $($rest)*));
+ (ps $($rest:tt)*) => (case!(@parse PartialEq::eq, $($rest)*));
+
+ (@parse $cmp:expr, $src:expr, [$($e:tt)*]) => ({
+ let expected = SyntaxTree { nodes: list!(nodes [$($e)*]) };
+ let found = parse($src, ParseContext { scope: &scope() }).0;
+ ($cmp(&found, &expected), expected, found)
+ });
+
+ (c $src:expr, [$($e:tt)*]) => ({
+ let expected = Colorization { tokens: list!(colors [$($e)*]) };
+ let found = parse($src, ParseContext { scope: &scope() }).1;
+ (expected == found, expected, found)
+ });
+
+ (e $src:expr, [$($e:tt)*]) => ({
+ let expected = ErrorMap { errors: list!([$($e)*]) };
+ let found = parse($src, ParseContext { scope: &scope() }).2;
+ (expected == found, expected, found)
+ });
+}
+
+/// A scope containing the `DebugFn` as a fallback.
+fn scope() -> Scope {
+ Scope::with_debug::<DebugFn>()
+}
+
+/// Parses possibly-spanned lists of token or node expressions.
+macro_rules! list {
+ (expr [$($item:expr),* $(,)?]) => ({
+ #[allow(unused_imports)]
+ use cuts::expr::*;
+ Tuple { items: vec![$(zspan($item)),*] }
+ });
+
+ (expr [$($key:expr =>($_:expr)=> $value:expr),* $(,)?]) => ({
+ #[allow(unused_imports)]
+ use cuts::expr::*;
+ Object {
+ pairs: vec![$(Pair {
+ key: zspan(Ident($key.to_string())),
+ value: zspan($value),
+ }),*]
+ }
+ });
+
+ ($cut:ident [$($e:tt)*]) => ({
+ #[allow(unused_imports)]
+ use cuts::$cut::*;
+ list!([$($e)*])
+ });
+
+ ([$(($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)),* $(,)?]) => ({
+ vec![
+ $(Spanned { v: $v, span: Span {
+ start: Position { line: $sl, column: $sc },
+ end: Position { line: $el, column: $ec },
+ }}),*
+ ]
+ });
+
+ ([$($e:tt)*]) => (vec![$($e)*].into_iter().map(zspan).collect::<Vec<_>>());
+}
+
+/// Composes a function expression.
+macro_rules! func {
+ ($name:expr $(,pos: [$($p:tt)*])? $(,key: [$($k:tt)*])?; $($b:tt)*) => ({
+ #![allow(unused_mut, unused_assignments)]
+
+ let mut positional = Tuple::new();
+ let mut keyword = Object::new();
+
+ $(positional = list!(expr [$($p)*]);)?
+ $(keyword = list!(expr [$($k)*]);)?
+
+ Node::Func(FuncCall(Box::new(DebugFn {
+ header: FuncHeader {
+ name: zspan(Ident($name.to_string())),
+ args: FuncArgs {
+ positional,
+ keyword,
+ },
+ },
+ body: func!(@body $($b)*),
+ })))
+ });
+
+ (@body Some($($b:tt)*)) => (Some(SyntaxTree { nodes: list!(nodes $($b)*) }));
+ (@body None) => (None);
+}
+
+function! {
+ /// Most functions in the tests are parsed into the debug function for easy
+ /// inspection of arguments and body.
+ #[derive(Debug, PartialEq)]
+ pub struct DebugFn {
+ header: FuncHeader,
+ body: Option<SyntaxTree>,
+ }
+
+ parse(header, body, ctx) {
+ DebugFn {
+ header: header.clone(),
+ body: parse!(optional: body, ctx),
+ }
+ }
+
+ layout() { vec![] }
+}
+
+/// Span an element with a zero span.
+fn zspan<T>(v: T) -> Spanned<T> {
+ Spanned { v, span: Span::ZERO }
+}
+
+/// Abbreviations for tokens, nodes, colors and expressions.
+#[allow(non_snake_case, dead_code)]
+mod cuts {
+ pub mod tokens {
+ pub use typstc::syntax::Token::{
+ Whitespace as W,
+ LineComment as LC,
+ BlockComment as BC,
+ StarSlash as SS,
+ LeftBracket as LB,
+ RightBracket as RB,
+ LeftParen as LP,
+ RightParen as RP,
+ LeftBrace as LBR,
+ RightBrace as RBR,
+ Colon as CL,
+ Comma as CM,
+ Equals as EQ,
+ ExprIdent as ID,
+ ExprStr as STR,
+ ExprSize as SIZE,
+ ExprNumber as NUM,
+ ExprBool as BOOL,
+ Star as S,
+ Underscore as U,
+ Backtick as B,
+ Text as T,
+ };
+ }
+
+ pub mod nodes {
+ use typstc::syntax::Node;
+
+ pub use Node::{
+ Space as S,
+ Newline as N,
+ ToggleItalic as I,
+ ToggleBolder as B,
+ ToggleMonospace as M,
+ };
+
+ pub fn T(text: &str) -> Node {
+ Node::Text(text.to_string())
+ }
+ }
+
+ pub mod colors {
+ pub use typstc::syntax::ColorToken::{
+ Comment as C,
+ Bracket as B,
+ FuncName as FN,
+ Colon as CL,
+ Key as K,
+ Equals as EQ,
+ Comma as CM,
+ Paren as P,
+ Brace as BR,
+ ExprIdent as ID,
+ ExprStr as STR,
+ ExprNumber as NUM,
+ ExprSize as SIZE,
+ ExprBool as BOOL,
+ Bold as BD,
+ Italic as IT,
+ Monospace as MS,
+ Invalid as INV,
+ };
+ }
+
+ pub mod expr {
+ use typstc::syntax::{Expression, Ident};
+
+ pub use Expression::{
+ Number as NUM,
+ Size as SIZE,
+ Bool as BOOL,
+ };
+
+ pub fn ID(text: &str) -> Expression {
+ Expression::Ident(Ident(text.to_string()))
+ }
+
+ pub fn STR(text: &str) -> Expression {
+ Expression::Str(text.to_string())
+ }
+ }
+}
+
+fn main() {
+ test(include!("../cache/parser-tests.rs"))
+}
diff --git a/tests/render.py b/tests/src/render.py
index 1387ed53..bb27e973 100644
--- a/tests/render.py
+++ b/tests/src/render.py
@@ -7,7 +7,7 @@ from PIL import Image, ImageDraw, ImageFont
BASE = os.path.dirname(__file__)
-CACHE = os.path.join(BASE, 'cache/')
+CACHE = os.path.join(BASE, '../cache/')
SERIAL = os.path.join(CACHE, 'serial/')
RENDER = os.path.join(CACHE, 'render/')
@@ -98,16 +98,18 @@ class MultiboxRenderer:
class BoxRenderer:
- def __init__(self, fonts, width, height):
+ def __init__(self, fonts, width, height, grid=False):
self.fonts = fonts
self.size = (pix(width), pix(height))
img = Image.new('RGBA', self.size, (255, 255, 255, 255))
pixels = numpy.array(img)
- # for i in range(0, int(height)):
- # for j in range(0, int(width)):
- # if ((i // 2) % 2 == 0) == ((j // 2) % 2 == 0):
- # pixels[4*i:4*(i+1), 4*j:4*(j+1)] = (225, 225, 225, 255)
+
+ if grid:
+ for i in range(0, int(height)):
+ for j in range(0, int(width)):
+ if ((i // 2) % 2 == 0) == ((j // 2) % 2 == 0):
+ pixels[4*i:4*(i+1), 4*j:4*(j+1)] = (225, 225, 225, 255)
self.img = Image.fromarray(pixels, 'RGBA')
self.draw = ImageDraw.Draw(self.img)
diff --git a/tests/src/spanless.rs b/tests/src/spanless.rs
new file mode 100644
index 00000000..fde5a2ed
--- /dev/null
+++ b/tests/src/spanless.rs
@@ -0,0 +1,62 @@
+use super::*;
+
+
+/// Compares elements by only looking at values and ignoring spans.
+pub trait SpanlessEq<T> {
+ fn spanless_eq(&self, other: &T) -> bool;
+}
+
+impl SpanlessEq<Vec<Spanned<Token<'_>>>> for Vec<Spanned<Token<'_>>> {
+ fn spanless_eq(&self, other: &Vec<Spanned<Token>>) -> bool {
+ self.len() == other.len()
+ && self.iter().zip(other).all(|(x, y)| x.v == y.v)
+ }
+}
+
+impl SpanlessEq<SyntaxTree> for SyntaxTree {
+ fn spanless_eq(&self, other: &SyntaxTree) -> bool {
+ fn downcast(func: &FuncCall) -> &DebugFn {
+ func.0.downcast::<DebugFn>().expect("not a debug fn")
+ }
+
+ self.nodes.len() == other.nodes.len()
+ && self.nodes.iter().zip(&other.nodes).all(|(x, y)| match (&x.v, &y.v) {
+ (Node::Func(a), Node::Func(b)) => downcast(a).spanless_eq(downcast(b)),
+ (a, b) => a == b,
+ })
+ }
+}
+
+impl SpanlessEq<DebugFn> for DebugFn {
+ fn spanless_eq(&self, other: &DebugFn) -> bool {
+ self.header.name.v == other.header.name.v
+ && self.header.args.positional.spanless_eq(&other.header.args.positional)
+ && self.header.args.keyword.spanless_eq(&other.header.args.keyword)
+ }
+}
+
+impl SpanlessEq<Expression> for Expression {
+ fn spanless_eq(&self, other: &Expression) -> bool {
+ match (self, other) {
+ (Expression::Tuple(a), Expression::Tuple(b)) => a.spanless_eq(b),
+ (Expression::Object(a), Expression::Object(b)) => a.spanless_eq(b),
+ (a, b) => a == b,
+ }
+ }
+}
+
+impl SpanlessEq<Tuple> for Tuple {
+ fn spanless_eq(&self, other: &Tuple) -> bool {
+ self.items.len() == other.items.len()
+ && self.items.iter().zip(&other.items)
+ .all(|(x, y)| x.v.spanless_eq(&y.v))
+ }
+}
+
+impl SpanlessEq<Object> for Object {
+ fn spanless_eq(&self, other: &Object) -> bool {
+ self.pairs.len() == other.pairs.len()
+ && self.pairs.iter().zip(&other.pairs)
+ .all(|(x, y)| x.key.v == y.key.v && x.value.v.spanless_eq(&y.value.v))
+ }
+}