Ray's Knowledge Base

Check a hand-written parser's grammar with a Lark LALR(1) copy and one shared sample file

RecipeVerified 27 Sep 2026Holds anywhere
Recipe. When to use it, the steps, and what showed that they work.

When to use#

A language has a hand-written recursive-descent parser. Such a parser never reports an ambiguity: when two readings are possible it takes the branch it tries first, so users get a surprising parse and no error.

Steps#

  1. Restate the grammar as a Lark grammar (scripts/<lang>.lark) and build it with Lark(grammar, parser="lalr", lexer="contextual", debug=True), collecting every shift/reduce and reduce/reduce conflict. Fail on any conflict.

  2. Keep the samples in one file both parsers read, for example tests/<lang>/grammar_samples.<ext>: samples separated by a line ---, and a line ### invalid that starts the samples that must not parse.

  3. The Lark script checks: no conflicts, every valid sample parses, every invalid sample fails. The native test checks the same file, plus the round trip parse, print, parse and tree equality.

  4. Run the Lark script without installing anything: uv run --no-project --with lark python scripts/check_<lang>_grammar.py --samples tests/<lang>/grammar_samples.<ext>.

  5. Register it as a CTest that is skipped, not failed, when uv is missing:

    find_program(UV_EXECUTABLE uv)
    if(UV_EXECUTABLE)
      add_test(NAME lang_grammar COMMAND ${UV_EXECUTABLE} run --no-project --with lark python ${CMAKE_SOURCE_DIR}/scripts/check_lang_grammar.py --samples ${CMAKE_CURRENT_SOURCE_DIR}/grammar_samples.lang)
    else()
      add_test(NAME lang_grammar COMMAND ${CMAKE_COMMAND} -E echo "uv not found; grammar check skipped")
      set_tests_properties(lang_grammar PROPERTIES SKIP_REGULAR_EXPRESSION "skipped")
    endif()
    
  6. When the native parser gains a form, add a sample; the check then fails until the Lark copy accepts the same language, so the two cannot drift silently.

Evidence#

  • dftracer-utils duql stage 12a1 (2026-09-27): the first Lark run found four real ambiguities in a grammar that had been designed and audited: sort - (direction or unary minus, reduce/reduce), pivot k in [..] versus the in operator, a legacy "text" in field form versus x in [..], and optional , separators between block members. Later, a sample for tags.0 found that the Lark copy lacked numeric path keys.
  • Final state: 55 valid and 20 invalid samples, no conflicts, duql_grammar CTest.