TextMate grammar for the recipe pharmacological notation language,
generated from the same JavaScript vocabulary modules that drive
tree-sitter-recipe's parser.
Ships as recipe.tmLanguage.json (the package's main export) — load it in
Shiki, a VS Code language extension, Monaco (via monaco-textmate), or any
other vscode-textmate host.
The parser's vocabulary (FREQUENCY, TIMING, ROUTE, …, UNITS, plus the
Dutch patient-prose frequency vocab in grammar/dutch) lives in plain
readonly string[] modules under
tree-sitter-recipe/grammar/. Authoring a parallel TextMate grammar by hand
would mean copying those lists into regex alternations and re-copying them
every time the parser is touched. Drift guaranteed.
Instead, src/grammar.ts imports those exact modules through
tree-sitter-recipe's package exports map, escapes metachars, sorts
alternatives longest-first (TextMate is first-match, not longest-match like
tree-sitter), and emits a ready-to-ship JSON grammar. One source of truth,
no sync burden.
Captures from tree-sitter-recipe/queries/highlights.scm are mapped to
standard TextMate scopes (keyword.control.…, support.function.…,
invalid.illegal.…, …) with a trailing .recipe namespace. Every mainstream
theme already paints these — no custom theme needs to be shipped with the
grammar.
| highlights.scm capture | TextMate scope |
|---|---|
@keyword.directive |
keyword.control.directive.{rx,dispense,signa}.recipe |
@keyword.repeat |
keyword.other.frequency.recipe |
@keyword (timing) |
keyword.other.timing.recipe |
@function.macro |
support.function.route.recipe |
@attribute |
entity.other.attribute-name.recipe |
@keyword.error |
invalid.illegal.warning.recipe |
@type (form) |
storage.type.form.recipe |
@keyword.operator |
keyword.operator.{compounding,fill,dtd}.recipe |
@keyword.conditional |
keyword.control.conditional.recipe |
@number |
constant.numeric.recipe |
@type.builtin (unit) |
support.type.unit.recipe |
@variable (ingredient) |
variable.other.ingredient.recipe |
@string (signa) |
string.unquoted.signa.recipe |
@comment |
comment.{line.number-sign,block}.recipe |
@comment.documentation |
comment.{line,block}.documentation.recipe |
@punctuation.delimiter |
punctuation.separator.recipe |
recipe-tmlanguage/
├── bin/
│ └── recipe-tmlang.ts # DreamCLI entry — `recipe-tmlang generate|verify`
├── src/
│ ├── grammar.ts # pure: vocab → TextMate grammar object
│ └── verifier.ts # pure: tokenize fixtures, check scope assertions
├── recipe.tmLanguage.json # generated grammar (the main export)
└── recipe.tmLanguage.ts # typed re-export of the JSON
bun add recipe-tmlanguage # or: npm install recipe-tmlanguageThe grammar is pre-generated in the package — no build step for consumers.
tree-sitter-recipe is a regular npm dependency now, so a plain install is
all the setup there is:
bun install
bun run generate # rebuild recipe.tmLanguage.json
bun run verify # check scopes against the upstream fixturesPowered by DreamCLI — no hand-rolled argparse.
npx recipe-tmlanguage --help # or: bunx recipe-tmlanguage --helpnpx recipe-tmlanguage generate # → recipe.tmLanguage.json
npx recipe-tmlanguage generate --out foo.json # custom path
npx recipe-tmlanguage generate --indent 2 --quiet # 2-space indent, silent
npx recipe-tmlanguage generate --json # machine-readable on stdoutTokenizes every fixture in tree-sitter-recipe/test/highlight/ with
vscode-textmate + vscode-oniguruma (the same engine Shiki wraps) and
asserts the scope at each caret marker matches the tree-sitter capture.
Fixtures and the Oniguruma WASM are located via package exports — no
relative paths to maintain.
npx recipe-tmlanguage verify
npx recipe-tmlanguage verify --json # { pass, total, failures[] }
npx recipe-tmlanguage verify --max-failures 0 # print every failureCurrent status: 149 / 149 fixture assertions pass.
Don't register the raw JSON yourself — its grammar name is Recipe, which
won't match a lang: "recipe" lookup. Reach for recipe-shiki
instead: it wraps this grammar in a LanguageRegistration with the canonical
lang recipe (and alias rx).
For any other vscode-textmate host, load recipe.tmLanguage.json directly —
grammar name Recipe, scope source.recipe. The scopes are standard TextMate,
so any theme paints recipe blocks immediately.
MIT © Kaj Kowalski