|
| 1 | +# tree-sitter-andy-cpp |
| 2 | + |
| 3 | +A [tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar for the |
| 4 | +**Andy C++** (`.ndc`) language. |
| 5 | + |
| 6 | +Tree-sitter powers incremental, error-tolerant syntax trees used by editors such |
| 7 | +as **Neovim**, **Helix**, **Zed**, and **Emacs** for highlighting, structural |
| 8 | +selection, folding, and code navigation. (VS Code does not use tree-sitter for |
| 9 | +highlighting — it uses the TextMate grammar in `../andy-cpp/syntaxes/`.) |
| 10 | + |
| 11 | +The grammar mirrors the precedence ladder and constructs implemented in |
| 12 | +`ndc_lexer` / `ndc_parser`. It is validated against the interpreter's full |
| 13 | +functional-test corpus: every valid `.ndc` program under |
| 14 | +`tests/functional/programs/` parses without errors. |
| 15 | + |
| 16 | +## Layout |
| 17 | + |
| 18 | +``` |
| 19 | +grammar.js # the grammar definition |
| 20 | +tree-sitter.json # package metadata (generated/maintained by the CLI) |
| 21 | +queries/ |
| 22 | + highlights.scm # syntax highlighting |
| 23 | + locals.scm # scopes & definitions (variables, params, functions) |
| 24 | + injections.scm # `#!` shebang line highlighted as bash |
| 25 | +test/corpus/ # tree-sitter test cases |
| 26 | +src/ # generated parser (run `tree-sitter generate`) |
| 27 | +``` |
| 28 | + |
| 29 | +## Developing |
| 30 | + |
| 31 | +Requires Node.js. The tree-sitter CLI is a dev dependency. |
| 32 | + |
| 33 | +```bash |
| 34 | +cd ext/tree-sitter-andy-cpp |
| 35 | +npm install # installs tree-sitter-cli |
| 36 | +npx tree-sitter generate # regenerate src/parser.c from grammar.js |
| 37 | +npx tree-sitter test # run test/corpus |
| 38 | +npx tree-sitter parse path.ndc # dump the parse tree for a file |
| 39 | +``` |
| 40 | + |
| 41 | +Re-run `generate` after every edit to `grammar.js`. Commit the regenerated |
| 42 | +`src/` so consumers can build without the CLI. |
| 43 | + |
| 44 | +### Re-validating against the interpreter corpus |
| 45 | + |
| 46 | +```bash |
| 47 | +cd ext/tree-sitter-andy-cpp |
| 48 | +fail=0 |
| 49 | +for f in $(find ../../tests/functional/programs -name '*.ndc'); do |
| 50 | + npx tree-sitter parse -q "$f" >/dev/null 2>&1 || { echo "ERR $f"; fail=1; } |
| 51 | +done |
| 52 | +[ $fail -eq 0 ] && echo "all valid programs parse" |
| 53 | +``` |
| 54 | + |
| 55 | +The only files that report errors are the interpreter's deliberate |
| 56 | +`// expect-error:` cases (malformed input) — that is the expected outcome. |
| 57 | + |
| 58 | +## Editor integration |
| 59 | + |
| 60 | +The parser's language name is **`andy_cpp`** (the symbol exported by the |
| 61 | +generated parser is `tree_sitter_andy_cpp`). |
| 62 | + |
| 63 | +The instructions below drive each editor's **built-in** tree-sitter runtime, so |
| 64 | +they don't depend on a plugin manager or a specific nvim-treesitter version. |
| 65 | +Building the parser needs Node.js and a C compiler. |
| 66 | + |
| 67 | +### Optional: a language server |
| 68 | + |
| 69 | +The interpreter ships an LSP server, started with `ndc lsp` over stdio. It |
| 70 | +provides hover (inferred types), completion, go-to-definition, document symbols |
| 71 | +and inlay hints. Install the `ndc` binary so it's on your `PATH`: |
| 72 | + |
| 73 | +```bash |
| 74 | +cargo install --git https://github.com/timfennis/andy-cpp |
| 75 | +``` |
| 76 | + |
| 77 | +The editor sections below wire this up alongside highlighting. |
| 78 | + |
| 79 | +### Neovim |
| 80 | + |
| 81 | +Neovim has a built-in tree-sitter runtime, so nvim-treesitter is not required to |
| 82 | +load this grammar. |
| 83 | + |
| 84 | +1. Build the parser and install it with the queries where Neovim's runtimepath |
| 85 | + can find them (the output file must be named `andy_cpp.so`): |
| 86 | + |
| 87 | + ```bash |
| 88 | + cd ext/tree-sitter-andy-cpp |
| 89 | + npm install |
| 90 | + mkdir -p ~/.config/nvim/parser ~/.config/nvim/queries/andy_cpp |
| 91 | + npx tree-sitter build -o ~/.config/nvim/parser/andy_cpp.so |
| 92 | + cp queries/*.scm ~/.config/nvim/queries/andy_cpp/ |
| 93 | + ``` |
| 94 | + |
| 95 | +2. Add to your config (`init.lua`): |
| 96 | + |
| 97 | + ```lua |
| 98 | + -- Treat .ndc files as the `andy_cpp` filetype. |
| 99 | + vim.filetype.add({ extension = { ndc = "andy_cpp" } }) |
| 100 | + |
| 101 | + -- Start tree-sitter highlighting for those buffers. |
| 102 | + vim.api.nvim_create_autocmd("FileType", { |
| 103 | + pattern = "andy_cpp", |
| 104 | + callback = function(args) |
| 105 | + pcall(vim.treesitter.start, args.buf, "andy_cpp") |
| 106 | + end, |
| 107 | + }) |
| 108 | + |
| 109 | + -- Language server (Neovim 0.11+). Skip if you didn't install `ndc`. |
| 110 | + vim.lsp.config("ndc_lsp", { |
| 111 | + cmd = { "ndc", "lsp" }, |
| 112 | + filetypes = { "andy_cpp" }, |
| 113 | + root_markers = { ".git" }, -- falls back to the file's directory |
| 114 | + }) |
| 115 | + vim.lsp.enable("ndc_lsp") |
| 116 | + |
| 117 | + -- Optional: show inlay hints once the server attaches. |
| 118 | + vim.api.nvim_create_autocmd("LspAttach", { |
| 119 | + callback = function(args) |
| 120 | + local client = vim.lsp.get_client_by_id(args.data.client_id) |
| 121 | + if client and client.name == "ndc_lsp" then |
| 122 | + pcall(vim.lsp.inlay_hint.enable, true, { bufnr = args.buf }) |
| 123 | + end |
| 124 | + end, |
| 125 | + }) |
| 126 | + ``` |
| 127 | + |
| 128 | +Rebuild (step 1) after each `tree-sitter generate`, re-copy the queries after |
| 129 | +editing them, then restart Neovim. After rebuilding the `ndc` binary, reload the |
| 130 | +server with `:LspRestart`. |
| 131 | + |
| 132 | +> **Already map `.ndc` to a different filetype?** (for example, via an existing |
| 133 | +> `ftdetect` rule.) Keep that filetype, drop the `vim.filetype.add` call, and |
| 134 | +> point the parser at it with |
| 135 | +> `vim.treesitter.language.register("andy_cpp", "<your_filetype>")`. Use |
| 136 | +> `<your_filetype>` as the autocmd `pattern` and in the LSP `filetypes` list. |
| 137 | +
|
| 138 | +### Helix |
| 139 | + |
| 140 | +Helix has built-in tree-sitter and LSP support. Add to |
| 141 | +`~/.config/helix/languages.toml`: |
| 142 | + |
| 143 | +```toml |
| 144 | +[[language]] |
| 145 | +name = "andy-cpp" |
| 146 | +scope = "source.andy-cpp" |
| 147 | +file-types = ["ndc"] |
| 148 | +comment-tokens = ["//"] |
| 149 | +indent = { tab-width = 4, unit = " " } |
| 150 | +language-servers = ["ndc-lsp"] # omit if you didn't install `ndc` |
| 151 | + |
| 152 | +[language-server.ndc-lsp] |
| 153 | +command = "ndc" |
| 154 | +args = ["lsp"] |
| 155 | + |
| 156 | +[[grammar]] |
| 157 | +name = "andy-cpp" |
| 158 | +source = { git = "https://github.com/timfennis/andy-cpp", subpath = "ext/tree-sitter-andy-cpp" } |
| 159 | +``` |
| 160 | + |
| 161 | +Fetch and build the grammar, then install the queries: |
| 162 | + |
| 163 | +```bash |
| 164 | +hx --grammar fetch |
| 165 | +hx --grammar build |
| 166 | +mkdir -p ~/.config/helix/runtime/queries/andy-cpp |
| 167 | +cp ext/tree-sitter-andy-cpp/queries/*.scm ~/.config/helix/runtime/queries/andy-cpp/ |
| 168 | +``` |
| 169 | + |
| 170 | +## Known limitations |
| 171 | + |
| 172 | +- **Raw strings with embedded quotes** (`r#"he said "hi""#`) are not modelled; |
| 173 | + the token stops at the first `"`. Plain raw strings (`r"..."`, `r#"..."#` |
| 174 | + without inner quotes) work. None appear in the current corpus. |
| 175 | +- **Doubly-nested generics** in type annotations (`List<List<Int>>`) can |
| 176 | + mis-tokenize the closing `>>`. Single-level generics (`Map<String, Int>`, |
| 177 | + `Option<Any>`) are fine. |
| 178 | +- **Named augmented assignment** (`acc max= x`) is not highlighted specially, to |
| 179 | + avoid mis-tokenizing space-free comparisons like `a==b`. Symbolic compound |
| 180 | + operators (`+=`, `%%=`, `<>=`, …) are fully supported. |
0 commit comments