# antlrlab A command line version of [antlr-lab](http://lab.antlr.org/) written in C++. Give it your ANTLR grammar files and an input file, and it produces the parse tree diagram, the tree as text, the token list, and a syntax check of the input. ``` $ antlrlab examples/combined/Expr.g4 -t "1 + 2" (prog (expr (expr 1) + (expr 2)) ) Input is valid (3 tokens, 8 nodes) Report: antlr-lab-out/report.html ``` Open `antlr-lab-out/report.html` for the diagram (pan, zoom, save as SVG or PNG), the tree, the tokens, the problems and the grammar. Hovering a node or a token highlights the text it came from in the input. Prefer an editor? `antlrlab --serve Expr.g4 input.txt --open` starts a [web UI](#web-ui) where you edit the grammar and the inputs, save them to disk, and watch the diagram update. ## Build You need a C++17 compiler, CMake 3.16 or newer, Java 11 or newer (the ANTLR tool is a Java program), and network access for the first configure. The code is standard C++17 with the operating-system parts isolated in `src/platform.hpp` and `src/crash.hpp`, so it builds with **GCC**, **Clang**, **MSVC** (also `clang-cl`) and **MinGW-w64** (the *posix* thread model; the *win32* one has no `std::thread`), on **Linux, Windows and macOS**. Compilers older than GCC 9 or Clang 9 get `-lstdc++fs` / `-lc++fs` automatically. What has actually been run is listed under [Limitations](#limitations). ```sh cmake -B build -DANTLRLAB_DOWNLOAD_JAR=ON cmake --build build ./build/antlrlab --version ``` On Windows with Visual Studio, in a *Developer Command Prompt* or PowerShell (any generator works; Visual Studio's is multi-configuration, so name one): ```bat cmake -B build -DANTLRLAB_DOWNLOAD_JAR=ON cmake --build build --config Release build\Release\antlrlab.exe --selftest ``` With MinGW (MSYS2 `mingw-w64-x86_64-gcc` and `cmake`) use `-G "MinGW Makefiles"` or `-G Ninja`. To cross-compile for Windows from Linux: ```sh sudo apt install g++-mingw-w64-x86-64-posix cmake -B build-win -DCMAKE_TOOLCHAIN_FILE=cmake/mingw-w64-x86_64.cmake -DANTLRLAB_DOWNLOAD_JAR=ON cmake --build build-win ``` The configure step downloads the ANTLR C++ runtime (source, built statically) and, with `ANTLRLAB_DOWNLOAD_JAR=ON`, `antlr-4.13.2-complete.jar` (from antlr.org, or from the ANTLR website's GitHub repository if that is unreachable). The jar is also copied next to the program, where antlrlab looks for it. Without that option, tell antlrlab where the jar is when you run it (see below). | CMake option | Default | Meaning | | --- | --- | --- | | `ANTLR_VERSION` | `4.13.2` | Version of the runtime (and of the jar to download) | | `ANTLRLAB_DOWNLOAD_JAR` | `OFF` | Download the tool jar and use it as the default | | `ANTLRLAB_USE_SYSTEM_RUNTIME` | `OFF` | Link an installed runtime instead of downloading one | | `ANTLRLAB_STATIC_CRT` | `ON` | MSVC: link the C++ runtime statically (no redistributable needed) | | `ANTLRLAB_STATIC_LINK` | `ON` | MinGW: link libgcc, libstdc++ and winpthread statically (no DLLs to ship) | `.github/workflows/build.yml` builds and self-tests all of this (Linux with GCC and Clang, Windows with MSVC and MinGW, macOS) on every push. The tool and the runtime must be the same version. If you use your own jar or runtime, use the same release for both (4.10 or newer). ## Use ```sh # one combined grammar antlrlab Expr.g4 input.txt # separate lexer and parser grammars antlrlab -g CalcLexer.g4 -g CalcParser.g4 -i input.txt # re-run on every save, and keep report.html refreshed in your browser antlrlab Expr.g4 input.txt --watch --open # other rules, other input sources antlrlab Expr.g4 -s expr -t "1 + 2" echo "1 + 2" | antlrlab Expr.g4 -i - ``` | Option | Meaning | | --- | --- | | `-g, --grammar FILE` | Grammar file. Repeat for a lexer plus a parser. Files ending in `.g4` given as plain arguments count too. | | `-i, --input FILE` | Input file, or `-` for standard input. Any non-`.g4` plain argument is also taken as the input. | | `-t, --text TEXT` | Input given on the command line. | | `-s, --start RULE` | Start rule. Default: the first parser rule. | | `-o, --out DIR` | Output directory. Default: `antlr-lab-out`. | | `-l, --lib DIR` | Directory with imported grammars or `.tokens` files. | | `--antlr-jar FILE` | The ANTLR tool jar. | | `-w, --watch` | Re-run when a grammar or the input file changes. | | `--open` | Open `report.html` in the default browser. | | `--tokens` | Also print the token list. | | `--serve` | Start the [web UI](#web-ui) instead of running once. Plain arguments become open tabs. | | `-p, --port N` | Port for `--serve` (default: the first free port from 8765). | | `--root DIR` | Directory the web UI may read and write (default: the current directory). | | `-q, --quiet` | Print only diagnostics and the verdict. | | `--no-color` | No colors (`NO_COLOR` is honoured too). | | `--timeout SECS` | Stop a run that takes longer (default 120; `0` = no limit). Not enforced on Windows. | | `--no-isolate` | Run the ANTLR runtime inside the main process instead of a child process. For debuggers; see [If it crashes](#if-it-crashes). (Windows always does this.) | | `--selftest` | Check the installation with a built-in grammar, then exit. | Exit status: `0` the input is valid, `1` it has syntax errors, `2` a grammar, tool or usage error. That makes it usable in scripts and CI. ### Finding the ANTLR tool In this order: `--antlr-jar`, the `ANTLR_JAR` environment variable, the jar downloaded at build time, an `antlr-*-complete.jar` found in the current directory, next to the program, on `CLASSPATH`, in your home directory or in `/usr/local/lib`, `/usr/share/java` and the Homebrew directories (on Windows: `C:\Javalib`, `C:\antlr`, `C:\Program Files\ANTLR`), and finally an `antlr4` or `antlr` command on `PATH` (a `.exe`, `.cmd` or `.bat` on Windows). Java comes from `JAVA_HOME` or `PATH`. File names and paths may contain any Unicode characters. On Windows they are converted properly (the Windows ANSI code page is never used), and the Java tool, which only understands that code page, is given the short 8.3 form of a path when it contains characters outside it. On Linux, Java needs a UTF-8 locale (`LANG=C.UTF-8`) to open files with non-ASCII names. ### Output files | File | Content | | --- | --- | | `report.html` | The report: a single self-contained page, no network needed | | `tree.svg` | The diagram as a standalone SVG | | `tree.txt` | The tree in ANTLR's LISP form | | `tokens.txt` | Tokens as `[@index,start:stop='text',,line:col]` | | `problems.txt` | Syntax errors and warnings | | `generated/` | What the ANTLR tool wrote (the `.interp` files are what matters) | ## Web UI ```sh antlrlab --serve # browse the current directory antlrlab --serve Expr.g4 input.txt # start with these files open antlrlab --serve --root ~/grammars --open ``` antlrlab prints an address such as `http://127.0.0.1:8765/?token=...`; `--open` opens it for you. It is a local web app that edits **real files on disk**: ``` File Edit View +------------------------------+ +--------------------------------+ | Expr.g4 G | ok.txt I | in2 I | | Diagram (ok.txt) [valid] x | | Start rule [ ] Auto-run Run| | Diagram | Tree | Tokens | | | | | | one editor, one tab per | | the parse tree | | grammar or input file | +--------------------------------+ | | | Output (Expr) x | | | | Problems | ANTLR tool | +------------------------------+ +--------------------------------+ ``` - **One editor, many files.** Each grammar and input is a tab. The `G` / `I` badge says how the file is used; click it to switch (for example to parse a `.g4` file as input). The diagram follows the active input, the Output panel follows the active grammar. - **Run uses what is in the editor**, saved or not, so you can try an edit before touching the file. With *Auto-run* on (View menu) it re-runs shortly after you stop typing. The Java tool only runs again when a grammar actually changed. `Ctrl+Enter` runs immediately. - **Lexer + parser pairs work.** If the active grammar is a parser with `tokenVocab=X`, `X.g4` is taken from an open tab or from the same folder; if it is a lexer, the parser that uses it is found the same way. - **Real files.** `Ctrl+S` saves in place (line endings and BOM are kept), *Save as* picks a folder and name inside the workspace, *Open* browses it. Every two seconds the open files are checked against the disk: a clean tab reloads silently when another program changes the file; a tab with unsaved edits shows a banner instead, and saving over a changed file asks first. - **Errors where they are.** Syntax errors in the input are underlined and listed under *Problems*; ANTLR tool errors are marked in the grammar at the reported line. Click a problem to jump to it. Hovering or clicking a node in the diagram, or a row in *Tokens*, highlights its text in the input. - **Diagram** with zoom (wheel), pan (drag), *Fit*, and SVG / PNG export. *Tree* shows the LISP form (indented or on one line, with Copy), *Tokens* the token table. - **Editor niceties:** grammar syntax colors, line numbers, auto-indent, `Tab` / `Shift+Tab` indent, `Ctrl+/` comment, `Ctrl+F` find and replace. - Panels close with their `x` and come back from the View menu; the splitters drag. Layout, theme (system / light / dark) and text size are remembered, and so are the open files per workspace. | Shortcut | Action | | --- | --- | | `Ctrl+Enter` | Run | | `Ctrl+S`, `Ctrl+Shift+S` | Save, Save as | | `Ctrl+O` | Open file | | `Alt+W` | Close tab | | `Ctrl+F` | Find and replace | | `Ctrl+/` | Toggle comment (grammar) | ### Is it safe to leave running? It is built for your own machine, and a page that can write files deserves care: - It listens on `127.0.0.1` only, never on the network. - Every request needs a random token that is generated at start-up and sent in the printed address (it moves into a cookie and out of the address bar). The `Host` and `Origin` headers are checked, so other web pages and DNS-rebinding tricks cannot talk to it. - Reads and writes are confined to the workspace directory: paths are resolved (symlinks too) and anything outside is refused. `.git` is off limits, and hidden files are not listed. - It refuses to serve the root of the file system. Opening a file larger than 5 MB or an input larger than 4 MB is refused. ### Changing the UI The browser code lives in `web/` (plain HTML, CSS and JavaScript, no build tools or libraries). After editing it, run `python3 tools/embed_web.py` to regenerate `src/webui_assets.hpp`, which is checked in, so building antlrlab itself never needs Python. ## How it works The ANTLR C++ runtime can run a grammar but cannot read a `.g4` file, and compiling generated C++ for every change would be slow. So: 1. antlrlab runs the ANTLR tool (Java) on your grammars. The tool writes `.interp` files next to the generated code: the serialized grammar plus the token and rule names. 2. The `.interp` data is loaded into the C++ runtime's `LexerInterpreter` and `ParserInterpreter`, which run the grammar directly. No generated code is ever compiled. 3. The resulting parse tree is copied into a plain tree that the diagram, text and HTML code work from. The runtime part (steps 2 and 3) runs in an isolated worker, described under [If it crashes](#if-it-crashes). Like the online lab, actions and semantic predicates are ignored, and the parser does not insist on reaching the end of the input: if the start rule stops early you get a warning (end the rule with `EOF` to require a full match). ## If it crashes The ANTLR C++ runtime is a native library, and a bad grammar, a very deep input or a bug can crash it. antlrlab is built so that this does not take the program down: - The runtime runs in a **child process**, on a thread with a **512 MB stack** (falling back to smaller sizes if the system refuses). Deep recursion, which is the usual cause of crashes in the runtime on real-world grammars, simply works; and if something still crashes, only the child dies. The web UI keeps running and shows *Run crashed*; the command line prints the error, writes the report and exits with status 2 (in `--watch` mode it keeps watching). - A run longer than `--timeout` seconds (default 120) is stopped. - The loaded grammar data is checked against the rule names before the interpreters use it, so an inconsistent `.interp` file is reported as an error rather than causing an invalid memory access. When a crash happens, the terminal shows what it was doing and a stack trace: ``` [antlrlab] fatal error: SIGSEGV (invalid memory access, often a stack overflow) [antlrlab] while: parsing the input (parser) [antlrlab] address: 0x0 [antlrlab] stack trace (innermost first): ./antlrlab(_ZN6antlr4...+0x6f1)[0x558f71ae3e17] ... ``` Pipe the trace through `c++filt` to demangle the names. One hazard in the runtime is worked around on purpose. For some syntax errors its `ParserInterpreter` creates a token of its own and keeps it in a single slot; the next such error overwrites the slot and frees the old token, while the error node of the first error still points at it. Reading that node later is a use-after-free, which crashes some of the time (typically on inputs with several syntax errors). antlrlab therefore copies each error node's data while its token is still valid (`ErrorNodeRecorder` in `src/analysis.hpp`) and never touches those tokens afterwards. **First step: `antlrlab --selftest`.** It compiles a tiny built-in grammar and parses an input with it. If it passes, the installation is fine and the problem is specific to your grammar or input. If it fails, the tool and runtime are not working together: check that the jar and the runtime are the same version (`antlrlab --version` shows what it was built for) and rebuild from scratch. **To report a crash**, send the whole terminal output above, the grammar, the input (or a smaller input that still crashes), your operating system and compiler, and the output of `antlrlab --selftest`. **To debug it yourself**, run without the child process so that the debugger sees the crash: ```sh gdb --args ./antlrlab --no-isolate Grammar.g4 input.txt # then: run, bt lldb -- ./antlrlab --no-isolate Grammar.g4 input.txt # then: run, bt ``` Windows has no `fork`, so there the runtime runs in the main process, on a thread with a 512 MB stack (falling back to smaller ones) that antlrlab creates itself. A crash ends the program with exit status 2, but it is explained first: the terminal shows the exception, what antlrlab was doing and a stack trace (function names need debug symbols; without them you get `module+offset`). `--timeout` and `--no-isolate` have no effect on Windows. ## Limitations - Java is required at run time, because the grammar compiler exists only in Java. - Tool and runtime versions must match. A mismatch shows up as a message that the grammar could not be loaded. - Names that clash with C++ (a rule called `int`, an alternative label `# Int` is fine but `# int` is not) can make the tool report errors, because it generates C++. antlrlab carries on when the tool only complains about such clashes and still wrote its `.interp` files; otherwise rename the rule. - Very large trees (more than 20,000 nodes) skip the diagram; the tree text and tokens are still produced. - A run that takes longer than `--timeout` (default 120 seconds) is stopped (not on Windows). - Parse trees deeper than 50,000 levels are cut off (with a warning), and the indented tree text stops indenting after 40 levels. - The web UI was exercised in Chromium. Firefox and Safari should work (it uses only standard features) but have not been tried. - Tested: Linux with GCC 13 and Clang 18 (the headers also compile with libc++), and the Windows build cross-compiled with MinGW-w64 GCC 13 and run under Wine with a Windows JDK (self-test, the examples, Unicode paths, the web UI, crash reports). **Not yet run:** Visual C++, real Windows and macOS. They are covered by the CI workflow (`.github/workflows/build.yml`); push to GitHub and read its results, or build locally as described above. - On Windows the runtime is not isolated in a child process (see [If it crashes](#if-it-crashes)), so a crash or an endless run takes antlrlab with it. Run the web UI with a grammar you trust, or on Linux, if that matters. ## Layout ``` src/platform.hpp everything OS-specific: UTF-8 paths, arguments, processes, console, random src/main.cpp command line, console output, watch mode src/antlr_tool.hpp finds and runs the ANTLR tool src/interp.hpp reads .interp files src/analysis.hpp the only file that uses the ANTLR runtime src/render.hpp tree text and SVG layout src/report.hpp report.html (HTML, CSS and JavaScript) src/pipeline.hpp verdict and statistics shared by CLI and web UI src/isolate.hpp runs the runtime in a child process on a big stack, with a time limit src/crash.hpp crash reports: phase and stack trace (signals on POSIX, SEH + DbgHelp on Windows) src/http.hpp small loopback HTTP server src/json.hpp JSON parser for request bodies src/serve.hpp web UI backend: files, run, security checks src/webui_assets.hpp generated from web/ by tools/embed_web.py web/ the web UI (HTML, CSS, JavaScript) examples/ combined, split and JSON grammars with inputs ```