IPU Assembly — VS Code extension
Syntax highlighting for IPU VLIW assembly (.asm, .s, .asm.j2).
Generated, not written
The TextMate grammar in syntaxes/ is generated by ipu_as/gen_vscode.py from
two sources, and it is worth being precise about which is which:
- Structure — comments,
; / ;;, :, the identifier shape — from
get_parser().terminals via pattern.to_regexp(). No token text is restated.
- Vocabulary — which identifiers are instructions or registers — from
instruction_spec.py and the register enums.
The vocabulary cannot come from the parser: asm_grammar.lark has a single
catch-all TOKEN terminal, so the parser genuinely does not distinguish BKPT
from lr0 from my_label. Neither layer is written by hand; both are
enumerated.
This matters because the previous, hand-written grammar drifted from the
language: it coloured BEQ lr0, cr0, +1;; as valid (commas are a lex error) and
failed to recognise end: in BKPT;; end: BKPT;; as a label. Nothing detected
either, because nothing compared the grammar to the parser. That is what the
agreement test below now does.
Edits reflect automatically on the next build:
| Change |
Effect |
A terminal's value in asm_grammar.lark |
picked up automatically |
An instruction added to instruction_spec.py |
picked up automatically |
A new terminal named COMMENT_* / STRING_* / PUNCT_* / WS_* |
scoped by convention |
| A new terminal with an unrecognised name |
build fails, naming the terminal |
The last row is the one manual step, and it is deliberate: nothing can guess
what colour a brand-new lexical concept should be, so the build stops at the
moment of the change rather than quietly leaving it unhighlighted.
Regenerate
bazel run //vscode-ipu-asm:gen_vscode
syntaxes/ipu-asm.tmLanguage.json and language-configuration.json are build
outputs and are not committed.
The agreement test
test/agreement.js tokenizes a corpus twice — once with the Lark parser, once
with the exact engine VS Code ships (vscode-textmate + vscode-oniguruma) —
and fails on any disagreement about a token's boundaries or role. It runs in
.github/workflows/vscode-extension.yml.
test/corpus/edge_cases.asm covers constructs the six app kernels happen not to
use (mid-line labels, labels spelled like mnemonics, every numeric base). It
must keep assembling; a change that breaks it is a real grammar regression.
Install
Every push to master republishes the vscode-latest prerelease:
curl -LO https://github.com/rechefe/ipu-emulator/releases/download/vscode-latest/ipu-asm.vsix
code --install-extension ipu-asm.vsix
Diagnostics
The grammar colours tokens; it cannot say a program is wrong. A TextMate rule
assigns scopes to spans and has no notion of what the grammar expects next, and
validity is not a lexical property — whether a comma is legal depends on the
parser's state, not on the characters. BEQ lr0, cr0, +1;; would be coloured
perfectly normally.
So extension.js runs the real assembler and turns its errors into squiggles,
covering all three stages:
|
Example |
Reported at |
| template |
{% for x in %} |
the template line |
| parse |
BEQ lr0, cr0, +1;; |
the comma |
| encode |
BGT lr0 lr1;;, mac.ee r0;; |
the mnemonic |
It shells out to ipu-as check --json rather than speaking LSP, which keeps the
extension dependency-free — no vscode-languageclient, no bundled
node_modules, and the vsix stays a few kilobytes.
By default it runs the checker through Bazel. To avoid paying Bazel's startup
cost on every check, point ipuAsm.checkCommand at a virtualenv instead:
"ipuAsm.checkCommand": [".venv/bin/ipu-as", "check", "--json", "--input"]
The file path is appended to whatever you configure.
When diagnostics are not running
A broken toolchain must not paint the file red — that would be a diagnostic
about the environment masquerading as one about the code. But it must not be
silent either: "checker unavailable" and "your code is fine" look identical, and
silence is the worse failure.
So a status bar item reports which state you are in — $(check) IPU when
diagnostics are live, $(warning) IPU: checks off when they are not. Clicking
it, or running IPU Assembly: Why are diagnostics not running?, gives the
reason.
The most common reason: ipu-as check is added by the same change as this
extension, so a workspace on a branch predating it has no check subcommand
and diagnostics cannot run there. Highlighting still works — that is
self-contained in the vsix.
ipu-as check is a normal CLI too: it exits 1 on problems and prints
file:line:col: error: …, so it works in a pre-commit hook or CI.
Positions inside Jinja templates
The parser only ever sees rendered text, so a byte offset in what it parsed has
no general relationship to the file on screen — loops repeat lines, conditionals
drop them, whitespace control joins them.
Rather than guess afterwards by matching line content, the checker renders the
template a second time with each line tagged by its own line number. The tags
are # comments, so they are invisible to the parser, and Jinja carries them
through whatever it does to the text — so a diagnostic names the source line
exactly, including inside a loop body or below a block of {%- set -%}
aliases. The tagged render is used only when it is equivalent to the real one.
Where a construct cannot be tagged safely, or an error carries no position at
all, the diagnostic says so with (position approximate) rather than implying
byte accuracy.