All commands are subcommands of rawast. Run rawast --help for the list, rawast <command> --help for per-command options.
Parse a file through a grammar; emit the resulting AST as JSON on stdout.
rawast parse grammars/json.json file.json
rawast parse grammars/lefdef.rawast sky130_fd_sc_hd.lef
rawast parse grammars/gdsii.rawast layout.gds > layout.jsonOptions:
--pretty— indent the JSON output for human reading. Default is compact.--start RULE— pick a non-default top rule. For grammars with multiple entry points (e.g.lefdef.rawast'sLEF/DEF), required if the default isn't what you want.--profile— enable per-rule parse-time profiling; print a top-20 table on stderr after the parse finishes.--profile-top N— limit the profile table to top-N rules. Use--profile-top allfor the full list, no truncation.
Profiling example:
rawast parse grammars/lefdef.rawast sky130_fd_sc_hd.lef \
--profile --profile-top 30 > /dev/null
# stderr shows top 30 rules by inclusive parse time:
# rule calls fails ns pct depth
# ----------------------------------------------------------------------------------
# LAYER_BODY_ITEM 42184 0 18,201,512 24.1% 7
# MACRO_BODY 12044 0 12,331,805 16.3% 9
# ...Read a JSON-described value tree from <input.json> and save it through the grammar to stdout (binary-safe).
rawast save grammars/json.json ast.json > out.json
rawast save grammars/gdsii.rawast layout.json > layout.gds # binary outputOptions:
--pretty/--no-pretty— toggle the grammar's pretty-print pass. Defaults to on for human-readable text grammars; binary grammars (GDSII) ignore it.
Parse <file> with one grammar, then save through another. Useful for <format> → JSON conversions.
# GDSII binary → JSON text
rawast convert --read grammars/gdsii.rawast \
--write grammars/json.json \
layout.gds > layout.json
# .rawast → JSON form (round-trip self-host check)
rawast convert --read grammars/rawast.rawast \
--write grammars/rawast.json \
grammars/lefdef.rawast > lefdef.jsonRun the structural linter on a grammar. Outputs human-readable diagnostics; each issue identifies the offending rule by name, lists the problem, and points at fixes.
Detects:
- LL(k) ambiguity — Choices whose alternatives share a leading rule that can't be disambiguated by Key-path analysis within depth-4 lookahead. The engine handles these via always-on Choice-frame alt-failure recovery, so the warning is informational — restructure to factor out the shared prefix (eliminates the alt-failure cost), or accept the pattern as intentional fall-through.
- Prefix-collision — a non-strict
Keyin an earlier alternative whose literal is a strict prefix of aKeyin a later alternative. Without word-boundary checking, PEG would consume the shorter Key first and leave the longer keyword's branch unreachable (canonical"not"shadowing"notch"). Fix: write the shorter Key as'foo'(strict, word-bounded) or reorder so the longer Key comes first. - Shared-leading-ref — two or more Choice alternatives that, when entered, will FIRST invoke the same named rule. The classic exponential PEG trap:
choice { LEVEL_BINOP, NEXT }whereLEVEL_BINOPstarts with<NEXT>:lhs=@and has required items after — when the operator fails, PEG backtracks and re-parses NEXT for the passthrough alt. With chained precedence levels, this compounds to 2^N per call ((a)becomes 268M frames in 100s). Fix: use theNEXT (OP NEXT)*pattern (sequence dict with<NEXT>:lhs=@, repeat <LEVEL_TAIL>:tail[]=@). - Wildcard-rule-with-nested-Choice-type-emit anti-pattern — a sequence-dict rule with no top-level
:type=…discriminator whose body contains a nested Choice with type emits. Such rules act as wildcards at save dispatch and silently swallow types meant for sibling alts. - Raw-consume (
*) misuse —*not followed by a literal-key sibling in the same sequence.
rawast lint grammars/gdsii.rawast
# clean → exit 0, no output
rawast lint grammars/lefdef.rawast
# 2 issues, one per rule:
# informational [VIARULE_LAYER_SUB]: 3 alternatives (0, 6, 7) share
# first-token(s) {"K:ENCLOSURE", "K:RESISTANCE"} within LL(4)
# lookahead. Engine handles via alt-failure recovery; restructure
# to factor out the shared prefix if the alt-failure cost matters,
# or accept the pattern. See docs/AGENTS.md.Each issue starts with the severity tag (informational, prefix-collision, etc.), the rule name in [brackets], then the specific problem and fix. Multiple shared tokens on one Choice are coalesced into a single issue.
Parse a corpus of files; aggregate per-rule profiling stats across all of them.
# Per-file enumeration
rawast profile grammars/lefdef.rawast file1.lef file2.lef file3.lef
# Files listed in a manifest
rawast profile grammars/lefdef.rawast --from-file lef_list.txtOptions:
--from-file FILE— read input paths fromFILE, one per line. Lines starting with#are treated as comments and skipped.--ext EXT[,EXT…]— when an argument is a directory, glob for files with these extensions. Default:rawast.--by {time|count|fails}— sort key for the aggregate table.timesums inclusive ns per rule;counttotals entries;failstotals fail counts.--top N— show top-N rules.allfor full table.
rawast profile grammars/lefdef.rawast --from-file lef_corpus.txt \
--by time --top 50Emit an EBNF-flavoured Markdown reference for the grammar's input syntax on stdout. Suitable for committing as project documentation.
rawast docs grammars/gdsii.rawast > docs/gdsii-syntax.mdOptions:
--heading-level N— base heading level (default 1, so top heading is#).
Emit a Markdown reference for the grammar's value-tree shape (the dict / array / choice structure a producer constructs before calling save). The output is the inverse of docs: what a host tool builds, not what a user types.
rawast schema grammars/lefdef.rawast > docs/lefdef-data-shape.mdOptions:
--title TITLE— override the auto-derived title.--heading-level N— same semantics asdocs.
Generate a Pydantic v2 module whose models mirror the grammar's parse/save dict shape exactly. Output goes to stdout; redirect to a file you can import.
rawast pydantic grammars/lefdef.rawast > rawast_models.pyOptions:
--module-doc DOC— override the module docstring.
Round-trip contract:
import rawast, rawast_models
g = rawast.Grammar("lefdef")
ast = g.parse_file("sky130_fd_sc_hd.lef", start="LEF")
model = rawast_models.LEF.model_validate(ast)
dumped = model.model_dump(exclude_none=True, by_alias=True)
assert ast == dumped # round-trip guarantee
assert g.save(dumped, start="LEF") is not NoneRead a parsed file, validate it against the Pydantic model, and emit Python source that reconstructs the equivalent typed model. Output is a runnable module: when executed, it builds the same model and (optionally) saves it back to the original format.
rawast pydantic grammars/lefdef.rawast > models.py
rawast pycode grammars/lefdef.rawast sky130_fd_sc_hd.lef \
--start LEF --models-module models > fixture.py
# Run the generated module to regenerate the original file:
python fixture.py > sky130_fd_sc_hd.regenerated.lefOptions:
--start RULE— top rule for multi-top-rule grammars. Required forlefdef.rawast(LEForDEF).--models-module MODULE— Python import path to the Pydantic-models module. If omitted, models are generated and exec'd inline (the emitted code'sfrom … import *will need a real module to import from).
Use cases:
- Test fixtures — convert a real corpus file into self-contained Python that builds the same AST.
- Tutorials — readable typed code mirroring a real LEF/DEF/GDSII.
- Programmatic editing — start from a real file, edit in your IDE with autocomplete + Pydantic validation, save back through rawast.
Limitations:
- Output size grows linearly with input. Multi-MB inputs produce multi-MB Python sources. Practical for files <~5 MB; impractical for production-scale outputs.
- Repetitive content emits N separate constructor calls — no loop / comprehension synthesis.
Standard. --version prints the engine version (matches rawast.__version__ and pyproject.toml).
EXAMPLES.md— worked Python + CLI examples per capabilityFEATURES.md— what each command exposesGRAMMARS.md— bundled grammars by short nameARCHITECTURE.md— parser groups,use:, ignore policy