From 7ff83f9e7b94ecf034363b041a45203fff2bfc26 Mon Sep 17 00:00:00 2001 From: "Guillem L. Jara" <4lon3ly0@tutanota.com> Date: Wed, 13 May 2026 16:21:43 +0200 Subject: [PATCH] chore(parser): add test infra --- parser/src/ast.rs | 1 - parser/src/diagnostics.rs | 2 +- parser/src/sexpr.rs | 11 ++- parser/src/tests.rs | 138 ++++++++++++++++++++++++++++++++++---- 4 files changed, 135 insertions(+), 17 deletions(-) diff --git a/parser/src/ast.rs b/parser/src/ast.rs index a361196..a58bc83 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -41,7 +41,6 @@ pub enum Atom<'a> { TypedRegex(Slice<'a>), } -#[derive(Debug)] pub enum RulePattern<'a> { Expression(Expr<'a>), Range(Expr<'a>, Expr<'a>), diff --git a/parser/src/diagnostics.rs b/parser/src/diagnostics.rs index 595be30..bdc0547 100644 --- a/parser/src/diagnostics.rs +++ b/parser/src/diagnostics.rs @@ -7,7 +7,7 @@ use ariadne::{Color, Label, Report, ReportKind, Source}; use lexer::{LexingError, Span}; use thiserror::Error; -#[derive(Debug, Error, Clone)] +#[derive(Debug, Error, Clone, PartialEq)] pub enum ParsingError { #[error("{}", .0)] LexingError(LexingError), diff --git a/parser/src/sexpr.rs b/parser/src/sexpr.rs index 5588cc8..6810bb2 100644 --- a/parser/src/sexpr.rs +++ b/parser/src/sexpr.rs @@ -6,7 +6,7 @@ use std::fmt::{Debug, Display, Formatter, Result}; use crate::ast::{ - Atom, Body, Identifier, Place, Redirection, SimpleStatement, Statement, Variable, + Atom, Body, Identifier, Place, Redirection, RulePattern, SimpleStatement, Statement, Variable, }; const PRETTY_PRINT_INDENT: usize = 2; @@ -306,3 +306,12 @@ impl Debug for Redirection { } } } + +impl Debug for RulePattern<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Self::Expression(expr) => <_ as Debug>::fmt(expr, f), + Self::Range(on, off) => write!(f, "(Range {on:?} {off:?})"), + } + } +} diff --git a/parser/src/tests.rs b/parser/src/tests.rs index 6ac51fd..cf8376c 100644 --- a/parser/src/tests.rs +++ b/parser/src/tests.rs @@ -3,25 +3,135 @@ // For the full copyright and license information, please view the LICENSE // files that was distributed with this source code. -use std::fmt::Debug; - use bumpalo::Bump; use crate::{Ast, Lexer, Parser}; -fn parse<'a, T: Debug>( - source: &'a str, - arena: &'a Bump, - selector: impl for<'b> FnOnce(&'b Ast<'a>) -> &'b T, -) -> super::Result { - let mut parser = Parser::new(arena); - parser - .parse_top(&mut Lexer::new(source.as_bytes(), arena), true) - .map(|x| format!("{:?}", selector(x))) +fn parse<'a>(source: &'a str, arena: &'a Bump) -> super::Result<&'a Ast<'a>> { + let parser = arena.alloc(Parser::new(arena)); + parser.parse_top(&mut Lexer::new(source.as_bytes(), arena), true) +} + +// Behold! The Holy Macro to rule them all. +macro_rules! test_parser { + ( + $code:expr => { + $(loads: $loads:expr,)? + $(begin: $begin:expr,)? + $(end: $end:expr,)? + $(begin_file: $begin_file:expr,)? + $(end_file: $end_file:expr,)? + $(rules: $rules:expr,)? + $(concurrent: $concurrent:expr,)? + $(functions: $functions:expr,)? + } + ) => { + let arena = Bump::new(); + let code = parse($code, &arena).unwrap(); + + #[allow(unused_mut, unused_assignments)] + let _ = { + use ::std::{option::Option, primitive::str, assert_eq, format}; + + let mut loads: &[&str] = &[]; + let mut end: &[&str] = &[]; + let mut begin: &[&str] = &[]; + let mut begin_file: &[&str] = &[]; + let mut end_file: &[&str] = &[]; + let mut rules: &[(Option<&str>, Option<&str>)] = &[]; + let mut concurrent: &[(Option<&str>, Option<&str>)] = &[]; + let mut functions: &[(&str, &[&str], &str)] = &[]; + + $(loads = &$loads;)? + $(end = &$end;)? + $(begin = &$begin;)? + $(begin_file = &$begin_file;)? + $(end_file = &$end_file;)? + $(rules = &$rules;)? + $(concurrent = &$concurrent;)? + $(functions = &$functions;)? + + test_parser!( + @internal check |(a, b)| assert_eq!(a.as_bytes(), b.as_ref()); + loads => code.loads + ); + test_parser!( + @internal munch check_for_each code; + |(&a, b)| assert_eq!(a, &format!("{b:?}")); + begin, end, begin_file, end_file + ); + test_parser!( + @internal munch check_for_each code; + |((e_pattern, e_actions), b)| { + assert_eq!( + *e_pattern, + b.pattern.as_ref().map(|x| format!("{x:?}")).as_deref() + ); + assert_eq!( + *e_actions, + b.actions.as_ref().map(|x| format!("{x:?}")).as_deref() + ); + }; + rules, concurrent + ); + test_parser!( + @internal check |((e_name, e_args, e_body), (name, fun))| { + assert_eq!(e_name, &format!("{name:?}")); + test_parser!(@internal check + |(&a, b)| assert_eq!(a, &format!("{b:?}")); + e_args => fun.args + ); + assert_eq!(*e_body, format!("{:?}", fun.body)); + }; + functions => code.functions + ); + }; + }; + (@internal check $lambda:expr; $a:expr => $b:expr) => { + assert_eq!($a.len(), $b.len()); + $a.into_iter().zip(&$b).for_each($lambda); + }; + (@internal check_for_each $code:ident; $lambda:expr; $a:ident) => { + test_parser!(@internal check $lambda; $a => $code.$a); + }; + (@internal munch $method:ident $code:ident; $lambda:expr; $arg:ident, $($rest:tt)*) => { + test_parser!(@internal $method $code; $lambda; $arg); + test_parser!(@internal munch $method $code; $lambda; $($rest)*); + }; + (@internal munch $method:ident $code:ident; $lambda:expr; $arg:ident) => { + test_parser!(@internal $method $code; $lambda; $arg); + }; + (@internal munch $method:ident $code:ident; $lambda:expr;) => {}; } #[test] -fn test_if() { - let arena = Bump::new(); - parse("{ if (x == 2) print y; }", &arena, |x| x).unwrap(); +fn test_parser_meta_holy_macro() { + let source = " + @load \"lib_foo.1\"; + @load \"lib_bar.so\"; + + BEGIN { print 1 + 1 } + BEGIN { 2 + 2 == 4\nprint \"foo\" } + { if (a) print 2; } + $0 == \"lisp would be proud\"; + function foo(a, b) { print a ? b : c } + "; + test_parser!(source => { + loads: ["lib_foo.1", "lib_bar.so"], + begin: [ + "(body (Print (Add 1 1)))", + "(body (Eq (Add 2 2) 4) (Print \"foo\"))" + ], + rules: [ + (None, Some("(body (if awk::a (body (Print 2))))")), + (Some("(Eq (Record 0) \"lisp would be proud\")"), None), + ], + functions: [ + ( + "awk::foo", + &["awk::a", "awk::b"], + "(body (Print (?: awk::a awk::b awk::c)))" + ) + ], + }); }