#[allow(unused_imports)]
use metadol::lexer::{Lexer, Token};
use metadol::parser::Parser;
#[allow(unused_macros)]
macro_rules! lex_test {
($name:ident, $input:expr, $check:expr) => {
#[test]
fn $name() {
let mut lexer = Lexer::new($input);
let token = lexer.next_token();
let check_fn: fn(&Token) -> bool = $check;
assert!(
check_fn(&token),
"Input '{}' produced unexpected token: {:?}",
$input,
token
);
}
};
}
macro_rules! parse_test {
($name:ident, $input:expr) => {
#[test]
fn $name() {
let mut parser = Parser::new($input);
let result = parser.parse_expr(0);
assert!(
result.is_ok(),
"Failed to parse '{}': {:?}",
$input,
result.err()
);
}
};
}
#[allow(unused_macros)]
macro_rules! parse_error_test {
($name:ident, $input:expr) => {
#[test]
fn $name() {
let mut parser = Parser::new($input);
let result = parser.parse_expr(0);
assert!(
result.is_err(),
"Expected parse error for '{}', got: {:?}",
$input,
result
);
}
};
}
mod lexer_existence {
use super::*;
fn lexes_to_single_token(input: &str) -> bool {
let mut lexer = Lexer::new(input);
let token = lexer.next_token();
!format!("{:?}", token).contains("Error") && !format!("{:?}", token).contains("Eof")
}
fn lexes_without_error(input: &str) -> bool {
let mut lexer = Lexer::new(input);
let token = lexer.next_token();
!format!("{:?}", token).contains("Error")
}
#[test]
fn lex_gene() {
assert!(lexes_to_single_token("gene"));
}
#[test]
fn lex_trait() {
assert!(lexes_to_single_token("trait"));
}
#[test]
fn lex_system() {
assert!(lexes_to_single_token("system"));
}
#[test]
fn lex_constraint() {
assert!(lexes_to_single_token("constraint"));
}
#[test]
fn lex_evolves() {
assert!(lexes_to_single_token("evolves"));
}
#[test]
fn lex_exegesis() {
assert!(lexes_to_single_token("exegesis"));
}
#[test]
fn lex_fun() {
assert!(lexes_to_single_token("fun"));
}
#[test]
fn lex_return() {
assert!(lexes_to_single_token("return"));
}
#[test]
fn lex_if() {
assert!(lexes_to_single_token("if"));
}
#[test]
fn lex_else() {
assert!(lexes_to_single_token("else"));
}
#[test]
fn lex_match() {
assert!(lexes_to_single_token("match"));
}
#[test]
fn lex_for() {
assert!(lexes_to_single_token("for"));
}
#[test]
fn lex_while() {
assert!(lexes_to_single_token("while"));
}
#[test]
fn lex_loop() {
assert!(lexes_to_single_token("loop"));
}
#[test]
fn lex_break() {
assert!(lexes_to_single_token("break"));
}
#[test]
fn lex_continue() {
assert!(lexes_to_single_token("continue"));
}
#[test]
fn lex_where() {
assert!(lexes_to_single_token("where"));
}
#[test]
fn lex_in() {
assert!(lexes_to_single_token("in"));
}
#[test]
fn lex_requires() {
assert!(lexes_to_single_token("requires"));
}
#[test]
fn lex_provides() {
assert!(lexes_to_single_token("provides"));
}
#[test]
fn lex_law() {
assert!(lexes_to_single_token("law"));
}
#[test]
fn lex_type() {
assert!(lexes_to_single_token("type"));
}
#[test]
fn lex_has() {
assert!(lexes_to_single_token("has"));
}
#[test]
fn lex_is() {
assert!(lexes_to_single_token("is"));
}
#[test]
fn lex_use() {
assert!(lexes_to_single_token("use"));
}
#[test]
fn lex_module() {
assert!(lexes_to_single_token("module"));
}
#[test]
fn lex_pub() {
assert!(lexes_to_single_token("pub"));
}
#[test]
fn lex_true() {
assert!(lexes_to_single_token("true"));
}
#[test]
fn lex_false() {
assert!(lexes_to_single_token("false"));
}
#[test]
fn lex_plus() {
assert!(lexes_to_single_token("+"));
}
#[test]
fn lex_minus() {
assert!(lexes_to_single_token("-"));
}
#[test]
fn lex_star() {
assert!(lexes_to_single_token("*"));
}
#[test]
fn lex_slash() {
assert!(lexes_to_single_token("/"));
}
#[test]
fn lex_percent() {
assert!(lexes_to_single_token("%"));
}
#[test]
fn lex_eq() {
assert!(lexes_to_single_token("="));
}
#[test]
fn lex_eqeq() {
assert!(lexes_to_single_token("=="));
}
#[test]
fn lex_neq() {
assert!(lexes_to_single_token("!="));
}
#[test]
fn lex_lt() {
assert!(lexes_to_single_token("<"));
}
#[test]
fn lex_le() {
assert!(lexes_to_single_token("<="));
}
#[test]
fn lex_gt() {
assert!(lexes_to_single_token(">"));
}
#[test]
fn lex_ge() {
assert!(lexes_to_single_token(">="));
}
#[test]
fn lex_and() {
assert!(lexes_to_single_token("&&"));
}
#[test]
fn lex_or() {
assert!(lexes_to_single_token("||"));
}
#[test]
fn lex_bang() {
assert!(lexes_to_single_token("!"));
}
#[test]
fn lex_pipe() {
assert!(lexes_to_single_token("|>"));
}
#[test]
fn lex_compose() {
assert!(lexes_to_single_token(">>"));
}
#[test]
fn lex_arrow() {
assert!(lexes_to_single_token("->"));
}
#[test]
fn lex_fat_arrow() {
assert!(lexes_to_single_token("=>"));
}
#[test]
fn lex_colon() {
assert!(lexes_to_single_token(":"));
}
#[test]
fn lex_colon_eq() {
assert!(lexes_to_single_token(":="));
}
#[test]
fn lex_dot() {
assert!(lexes_to_single_token("."));
}
#[test]
fn lex_comma() {
assert!(lexes_to_single_token(","));
}
#[test]
fn lex_at() {
assert!(lexes_to_single_token("@"));
}
#[test]
fn lex_hash() {
assert!(lexes_to_single_token("#"));
}
#[test]
fn lex_question() {
assert!(lexes_to_single_token("?"));
}
#[test]
fn lex_quote() {
assert!(lexes_to_single_token("'"));
}
#[test]
fn lex_lbrace() {
assert!(lexes_to_single_token("{"));
}
#[test]
fn lex_rbrace() {
assert!(lexes_to_single_token("}"));
}
#[test]
fn lex_lparen() {
assert!(lexes_to_single_token("("));
}
#[test]
fn lex_rparen() {
assert!(lexes_to_single_token(")"));
}
#[test]
fn lex_lbracket() {
assert!(lexes_to_single_token("["));
}
#[test]
fn lex_rbracket() {
assert!(lexes_to_single_token("]"));
}
#[test]
fn lex_int_zero() {
assert!(lexes_to_single_token("0"));
}
#[test]
fn lex_int_positive() {
assert!(lexes_to_single_token("42"));
}
#[test]
fn lex_int_large() {
assert!(lexes_to_single_token("1000000"));
}
#[test]
fn lex_float_simple() {
assert!(lexes_to_single_token("3.14"));
}
#[test]
fn lex_float_exp() {
assert!(lexes_to_single_token("1e10"));
}
#[test]
fn lex_string_empty() {
assert!(lexes_without_error("\"\""));
}
#[test]
fn lex_string_simple() {
assert!(lexes_without_error("\"hello\""));
}
#[test]
fn lex_ident_simple() {
assert!(lexes_to_single_token("foo"));
}
#[test]
fn lex_ident_underscore() {
assert!(lexes_to_single_token("foo_bar"));
}
#[test]
fn lex_ident_leading_underscore() {
assert!(lexes_to_single_token("_private"));
}
#[test]
fn lex_ident_with_numbers() {
assert!(lexes_to_single_token("var123"));
}
}
mod parser_success {
use super::*;
parse_test!(parse_int, "42");
parse_test!(parse_float, "3.14");
parse_test!(parse_string, "\"hello\"");
parse_test!(parse_true, "true");
parse_test!(parse_false, "false");
parse_test!(parse_ident, "foo");
parse_test!(parse_add, "1 + 2");
parse_test!(parse_sub, "1 - 2");
parse_test!(parse_mul, "1 * 2");
parse_test!(parse_div, "1 / 2");
parse_test!(parse_mod, "1 % 2");
parse_test!(parse_eq, "1 == 2");
parse_test!(parse_neq, "1 != 2");
parse_test!(parse_lt, "1 < 2");
parse_test!(parse_le, "1 <= 2");
parse_test!(parse_gt, "1 > 2");
parse_test!(parse_ge, "1 >= 2");
parse_test!(parse_and, "a && b");
parse_test!(parse_or, "a || b");
parse_test!(parse_precedence_1, "1 + 2 * 3");
parse_test!(parse_precedence_2, "1 * 2 + 3");
parse_test!(parse_precedence_3, "1 + 2 + 3");
parse_test!(parse_parens, "(1 + 2) * 3");
parse_test!(parse_nested_parens, "((1 + 2))");
parse_test!(parse_neg, "-1");
parse_test!(parse_not, "!true");
parse_test!(parse_call_no_args, "foo()");
parse_test!(parse_call_one_arg, "foo(1)");
parse_test!(parse_call_multi_args, "foo(1, 2, 3)");
parse_test!(parse_call_nested, "foo(bar(x))");
parse_test!(parse_call_chain, "foo()(x)");
parse_test!(parse_field, "obj.field");
parse_test!(parse_field_chain, "a.b.c");
parse_test!(parse_method_call, "obj.method()");
parse_test!(parse_method_chain, "obj.a().b().c()");
parse_test!(parse_index, "arr[0]");
parse_test!(parse_index_expr, "arr[i + 1]");
parse_test!(parse_index_chain, "matrix[i][j]");
parse_test!(parse_pipe, "x |> f");
parse_test!(parse_pipe_chain, "x |> f |> g");
parse_test!(parse_compose, "f >> g");
parse_test!(parse_compose_chain, "f >> g >> h");
parse_test!(parse_pipe_compose_mixed, "x |> f >> g");
parse_test!(parse_complex_1, "foo.bar(1 + 2).baz");
parse_test!(parse_complex_2, "arr[0].field");
parse_test!(parse_complex_3, "f(x)(y)(z)");
}
mod parser_declarations {
use metadol::parser::Parser;
fn parses_decl(input: &str) -> bool {
let mut parser = Parser::new(input);
parser.parse().is_ok()
}
fn parses_file(input: &str) -> bool {
let mut parser = Parser::new(input);
parser.parse_file().is_ok()
}
#[test]
fn decl_gene_empty() {
assert!(parses_decl("gene Empty { }"));
}
#[test]
fn decl_gene_with_type() {
assert!(parses_decl("gene Counter { type: Int64 }"));
}
#[test]
fn decl_gene_with_has() {
assert!(parses_decl("gene Container { has id: UInt64 }"));
}
#[test]
fn decl_gene_multi_fields() {
assert!(parses_decl("gene Point { has x: Float64 has y: Float64 }"));
}
#[test]
fn decl_fun_empty() {
assert!(parses_decl("fun noop() { }"));
}
#[test]
fn decl_fun_with_return() {
assert!(parses_decl("fun get() -> Int64 { return 42 }"));
}
#[test]
fn decl_fun_with_params() {
assert!(parses_decl(
"fun add(a: Int64, b: Int64) -> Int64 { return a + b }"
));
}
#[test]
fn decl_fun_pub() {
assert!(parses_decl("pub fun public_fn() { }"));
}
#[test]
fn decl_trait_empty() {
assert!(parses_decl("trait Empty { }"));
}
#[test]
fn decl_system_empty() {
assert!(parses_decl("system Empty { }"));
}
#[test]
fn decl_module_simple() {
assert!(parses_file("module my.module @ 1.0.0"));
}
#[test]
fn decl_use_simple() {
assert!(parses_file("use dol.ast"));
}
#[test]
fn decl_use_star() {
assert!(parses_file("use dol.ast.*"));
}
}
mod integration {
use metadol::parser::Parser;
use std::fs;
fn try_parse_file(path: &str) -> Result<(), String> {
let content = fs::read_to_string(path).map_err(|e| format!("Read error: {}", e))?;
let mut parser = Parser::new(&content);
parser
.parse_file()
.map(|_| ())
.map_err(|e| format!("Parse error: {:?}", e))
}
#[test]
fn parse_all_dol_files() {
let dol_dir = "dol";
if !std::path::Path::new(dol_dir).exists() {
println!("Skipping: dol/ directory not found");
return;
}
let mut passed = 0;
let mut failed = 0;
let mut errors = Vec::new();
for entry in fs::read_dir(dol_dir).unwrap() {
let path = entry.unwrap().path();
if path.extension().map(|e| e == "dol").unwrap_or(false) {
match try_parse_file(path.to_str().unwrap()) {
Ok(()) => passed += 1,
Err(e) => {
failed += 1;
errors.push(format!("{}: {}", path.display(), e));
}
}
}
}
if !errors.is_empty() {
println!("Parse failures:");
for e in &errors {
println!(" {}", e);
}
}
println!("Parsed {}/{} DOL files", passed, passed + failed);
}
}
mod codegen_smoke {
use metadol::codegen::RustCodegen;
use metadol::parser::Parser;
fn generates_code(input: &str) -> bool {
let mut parser = Parser::new(input);
let ast = match parser.parse_file() {
Ok(f) => f,
Err(_) => return false,
};
let output = RustCodegen::generate_all(&ast.declarations);
!output.trim().is_empty()
}
#[test]
fn codegen_simple_gene() {
assert!(generates_code("gene Counter { type: Int64 }"));
}
#[test]
fn codegen_gene_with_fields() {
assert!(generates_code(
"gene Point { has x: Float64 has y: Float64 }"
));
}
#[test]
fn codegen_function() {
assert!(generates_code(
"fun add(a: Int64, b: Int64) -> Int64 { return a + b }"
));
}
}