/// Integration tests for parsing Monad files /// Tests that the self hosted parser can parse all Monad source files use 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"