Something went wrong. Try again.
The Monad language. Dependent types, functional programming compiled with LLVM. Hobby project. monad-lang.org
dependent-types language compiler programming-language functional-programming
Something went wrong. Try again.
7.4 kB · 204 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205/// Integration tests for parsing Monad files/// Tests that the self hosted parser can parse all Monad source filesuse lang::parser {decls_parser}use parsec::core {}open ParseResult {fail, success}/// All init/ files to parse (safe - no box-drawing characters)#[partial]def init_files_safe : List String := ["init/src/id.mo", "init/src/lib.mo", "init/src/list.mo", "init/src/io.mo", "init/src/math.mo", "init/src/number.mo", "init/src/string.mo", "init/src/tests.mo"]/// All std/ files to parse// `std/derive.mo` added here specifically as the real-corpus// verification target for `defmacro`/decl-position macro-call parsing// (plans/bootstrapping/self-hosted-compiler.md's metaprogramming-// grammar plan, step 6) -- it has 4 real `defmacro NAME T := decls {// reflect_type_info! T ...meta }` declarations (Form A, with a nested// decl-position macro call inside each `decls{}` body), previously// unparseable by this self-hosted parser at all.#[partial]def std_files : List String := ["std/src/test.mo", "std/src/derive.mo", "std/src/path.mo", "std/src/io.mo", "std/src/process.mo", "std/src/lib.mo"]/// All examples/ files to parse (safe - no box-drawing characters)#[partial]def example_files_safe : List String := ["examples/do_block.mo", "examples/factorial.mo", "examples/hello.mo", "examples/indexed_monads.mo", "examples/iteration.mo", "examples/iteration_advanced.mo", "examples/pattern_matching.mo", "examples/structs.mo", "examples/test_mote.mo", "examples/tests.mo"]/// All lang/ files to parse (safe - no box-drawing characters)#[partial]def lang_files_safe : List String := ["lang/src/elaborate.mo", "cli/src/main.mo", "lang/src/module.mo", "lang/src/pretty.mo", "lang/src/scope.mo"]/// All init/ files with UTF-8 characters (previously blocked by byte indexing)#[partial]def init_files_utf8 : List String := ["init/src/string_profile.mo", "init/src/optics.mo"]/// All std/ files with UTF-8 characters#[partial]def std_files_utf8 : List String := ["std/src/test_map_full.mo", "std/src/list_tests3b.mo", "std/src/map.mo", "std/src/base.mo", "std/src/list.mo"]/// All examples/ files with UTF-8 characters#[partial]def example_files_utf8 : List String := ["examples/indexed_monads.mo", "examples/optics.mo"]/// All lang/ files with UTF-8 characters (self hosting)#[partial]def lang_files_utf8 : List String := ["lang/src/parser.mo", "lang/src/types.mo", "lang/src/codegen/emit.mo"]/// Parse a single file and return success status.////// `IO.read_file`'s contents are reached through `Monad.bind` -- the `do`/// block below -- rather than by matching the `IO` value's own `io`/// constructor, which is deliberately not ambient.#[partial]def parse_file (path : String) : IO Bool := do { let content <- IO.read_file (Path.path path); match decls_parser content { success _ _ => return true, fail _ => return false }}/// Parse multiple files#[partial]def parse_all (files : List String) : IO Bool := match files { List.empty => IO.pure true, List.cons f rest => Monad.bind (parse_file f) (fn ok => if ok then parse_all rest else IO.pure false) }// ================ Aggregate parse tests ================//// Only the `parse_all` aggregate tests are kept here -- the per-file// variants that used to sit alongside each of these (test_parse_init_id,// test_parse_lang_module, etc.) checked nothing an aggregate test didn't// already cover (`parse_all` fails the moment ANY listed file fails to// parse), so they were pure redundant surface area, not extra coverage.#[test]def test_parse_init_all_safe : IO Bool := parse_all init_files_safe#[test]def test_parse_std_all : IO Bool := parse_all std_files#[test]def test_parse_examples_all_safe : IO Bool := parse_all example_files_safe#[test]def test_parse_lang_all_safe : IO Bool := parse_all lang_files_safe#[test]def test_parse_init_all_utf8 : IO Bool := parse_all init_files_utf8#[test]def test_parse_std_all_utf8 : IO Bool := parse_all std_files_utf8#[test]def test_parse_examples_all_utf8 : IO Bool := parse_all example_files_utf8#[test]def test_parse_lang_all_utf8 : IO Bool := parse_all lang_files_utf8// test_parse_all_utf8_files removed: parsed the exact union of the four// category lists above via parse_all (which short-circuits on the first// failure), giving strictly worse debugging signal than the per-category// tests on any failure while adding no new coverage.// ================ Full-file-parse (no truncation) tests ================//// `decls_parser` is lenient: on a construct it can't parse, `decls_try`// silently stops and returns whatever it got so far as `success`, not a// `fail` — so a floor count like `I64.gt count 5` only proves "didn't// truncate to (near) zero", not "parsed the whole file". Checking// `success`'s own `remaining` field for emptiness is the actual contract// these tests care about: every byte of the file got consumed. (See// `lang/parser.mo`'s `decls_parser_strict` for the twin that turns// truncation into a hard `fail` instead of a silent partial `success` —// not used here so this file keeps exercising the lenient path real// callers actually use, just checking its result more precisely.)/// Whether a file's `decls_parser` run consumes the ENTIRE input, i.e./// nothing is left over in `success`'s own `remaining` field. A `fail` or/// a non-empty remainder both count as "didn't fully parse."#[partial]def file_fully_parses (path : String) : IO Bool := do { let content <- IO.read_file (Path.path path); match decls_parser content { success rem _ => return (String.is_empty rem), fail _ => return false }}#[test]def test_hello_fully_parses : IO Bool := file_fully_parses "examples/hello.mo"#[test]def test_string_fully_parses : IO Bool := file_fully_parses "init/src/string.mo"#[test]def test_scope_fully_parses : IO Bool := file_fully_parses "lang/src/scope.mo"// `lang/json.mo`/`lang/toml.mo`/`std/map.mo` all open with a `//`/`///`// comment containing an em dash (multi-byte UTF-8) before their very// first real declaration — before the `take_while`/`string_body` UTF-8// stepping fix (see `utf8_char_width`'s doc comment,// `lang/parser/combinators.mo`) this silently truncated the parse to// ZERO declarations (verified by bisection while landing that fix). All// three used to additionally stop short of their true decl count for// other reasons (juxtaposed list-literal application, `let` inside `if`// branches, paren type ascriptions, multi-param lambdas, multi-name// constructor fields, nested-paren class param types — each fixed// separately, motivated by exactly these files) — confirmed (2026-08-19)// that all three now parse to completion with zero bytes remaining, so// these tests assert that directly instead of a conservative floor.#[test]def test_json_fully_parses : IO Bool := file_fully_parses "lang/src/json.mo"#[test]def test_toml_fully_parses : IO Bool := file_fully_parses "lang/src/toml.mo"#[test]def test_map_fully_parses : IO Bool := file_fully_parses "std/src/map.mo"