Generate syntax highlighting from Macaulay2 itself - #27
Open
d-torrance wants to merge 1 commit into
Open
Conversation
Written by Claude Opus 5. The M2 wiki now documents both the expression language and the SimpleDoc docstring language, and the interpreter can report its own operator tables via getParsing. Use both instead of hand-maintaining this information. generate-grammar.m2 now writes syntaxes/m2-tokens.json -- builtin symbols, the symbolic operators classified by getParsing, and the SimpleDoc section keywords -- and scripts/generate-syntax.js expands the .in templates from it. Templating happens in node rather than M2 so that regenerating is checkable, and JSON templates are expanded structurally (parse, substitute, re-encode) rather than as text, which makes escaping correct by construction. Grammar changes: * Operators go from three patterns to every operator M2 defines, split into assignment and everything else. Alternations are sorted longest-first because Oniguruma is leftmost-first, and operators that are a strict prefix of one in the other group get a generated negative lookahead, without which "==" was scoped as two assignments. * Numbers are highlighted at all for the first time, including hex, octal, binary, and the p/e suffixes. The float pattern refuses a trailing dot so that 1..5 is not lexed as "1." followed by ".5". * Word keywords become keyword.control, distinct from types and functions. * /// is split three ways: doc/multidoc/document get the new SimpleDoc grammar, TEST gets real Macaulay2 highlighting, and a bare /// is a raw string with no inner patterns. Previously all three leaked code scopes into raw text. The SimpleDoc grammar follows the off-side rule using begin/while rules whose while pattern backreferences the indent captured by begin, so each section body is highlighted according to its own body form: code, verbatim, prose with @...@ escapes, item heads, or menu entries. Which body form each keyword takes cannot come from M2 -- it is decided inside the handler functions -- so it is hard-coded and checked against the keyword names M2 reports, and a keyword added upstream now fails the build. Also drive the formatter's operator list from the same data, which drops a bogus U+2298 that is not a Macaulay2 operator and adds the real U+22A0 and "<-"; a test had been pinning the bug. Give language-configuration a wordPattern that accepts ' and $ but not _, since _ is a binary operator. Add grammar tests: every pattern must compile as a JavaScript RegExp, since the webview REPL re-compiles them in the browser; the committed grammars must match their templates; and real tokenization pins the scopes. Install Macaulay2 in CI so the runtime tests stop silently skipping, and check that regenerating is a no-op. Delete the stale, unreferenced macaulay2.tmLanguage.yaml and a dead repository entry whose regex never compiled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
d-torrance
force-pushed
the
grammar-update
branch
from
August 12, 2026 04:18
bfabb27 to
8b8e434
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
The code and this description were written by Claude Opus 5. I reviewed
the result, but please read it with that in mind.
The two new wiki pages — Macaulay2 language grammar and SimpleDoc
language grammar — describe both languages this extension highlights, and
the interpreter can report its own operator tables through
getParsing. Thisuses both instead of maintaining that information by hand.
One caveat up front: operator precedence does not help a TextMate grammar.
It is a per-line regex scanner with no expression parser. What
getParsingactually buys is completeness, correct alternation ordering, and a
machine-checkable classification that fails loudly when Macaulay2 changes.
Generation
generate-grammar.m2writessyntaxes/m2-tokens.json(builtin symbols, the105 symbolic operators classified by
getParsing, and the SimpleDoc sectionkeywords), then
scripts/generate-syntax.jsexpands the.intemplates.JSON templates are expanded structurally — parse, substitute into string
values, re-encode — rather than as text, so escaping is correct by
construction. Doing it textually is a trap: a placeholder sits inside a JSON
string, so an operator alternation's backslashes need JSON escaping on top of
their regex escaping, and a bare
\.yields a grammar that will not parse.Delimiters (
(,),[,],{,},,,;,<|,|>) are keywordstoo and are excluded, or every parenthesis would be scoped as an operator.
Grammar
and everything else. Alternations are sorted longest-first because Oniguruma
is leftmost-first, not longest-match. Operators that are a strict prefix of
one in the other group get a generated negative lookahead — without it
==was scoped as two assignment=.0x/0o/0band thep/esuffixes. The float pattern refuses a trailing dot, so1..5is not lexed as the float1.followed by.5.keyword.control, distinct from types and functions.///is split three ways:doc/multidoc/documentget the newSimpleDoc grammar,
TESTgets real Macaulay2 highlighting, and a bare///is a raw string with no inner patterns. Previously all three leaked code
scopes into raw text — which is why a
--inside a raw string was paintedas a comment.
SimpleDoc
A new
text.macaulay2.simpledocgrammar. TextMate cannot count indentation ingeneral, but
begin/whilerules can backreference the indent captured bybegin, which is exactly the off-side rule (VS Code's own Markdown grammaruses the same trick). Each section body is then highlighted by its own body
form: Macaulay2 code, verbatim, prose with
@…@escapes, item heads, or menuentries.
Only whole-line
--is a comment, matchingSimpleDoc, so a trailing-- notein prose is no longer greyed out.Which body form each keyword takes cannot come from Macaulay2 — the tables
map a keyword to a handler function, and the body form is decided inside that
function. So it is hard-coded, and checked against the keyword names M2
reports: a keyword added upstream now fails the build instead of silently
going unhighlighted.
Known limits: detection must be on the same line as the
///(the tokenizersees one line at a time), and the "indentation floor only ever drops" quirk is
not reproduced.
Formatter and language configuration
The formatter's operator list is generated from the same data. That drops
⊘=(U+2298), which is not a Macaulay2 operator, and adds the real⊠=(U+22A0) and
<-. A test had been pinning the bug, so it changes here too.wordPatternnow accepts'and$, and deliberately excludes_: it is abinary operator, so
foo_baris three tokens and should not select as oneword.
Tests and CI
36 → 60 tests. Every pattern must compile as a JavaScript
RegExp, becausethe webview REPL re-compiles them in the browser; the committed grammars must
match their templates (pure JS, no M2 needed); and real tokenization pins the
scopes.
Macaulay2 is now installed in CI. The runtime test at
extension.test.tscallsthis.skip()when M2 is missing, so it had beensilently skipping on every run. CI also checks that regenerating is a no-op.
Three bugs were caught only by tokenizing for real, and were invisible from
reading the JSON: the
==split above,TEST ///never closing becauseapplyEndPatternLastlet the nested rule reopen instead of close, andwhilepatterns consuming the indent they matched, which silently disabled every
^-anchored rule.Also
Deletes the stale, unreferenced
macaulay2.tmLanguage.yamland a deadrepository.storageentry whose regex ((\)) never compiled.Worth a look
ppa:macaulay2/macaulay2, so the generated files are kept at thereleased symbol set.
isSkewAffineRingexists only in development buildsand has been removed from the committed output by hand; regenerating against
a development Macaulay2 will add it back, and the up-to-date check will then
fail until it reaches a release.
package.jsonstill maps.d/.ddto this grammar. Those are M2'sinterpreter sources in a different language, and sharper operator scoping
will make them look worse — probably its own issue.
🤖 Generated with Claude Code