This report analyzes the current tree-sitter grammar (grammar.js) against the
authoritative OMNeT++ NED-2 grammar and lexer (src/nedxml/ned2.y and
src/nedxml/ned2.lex), and recommends improvements so downstream tools
(editors, linters, doc generators, code navigation) can rely on the parser more
fully.
The current grammar is a solid foundation and parses real-world INET / Simu5G /
Veins files (see test/corpus/Regression). The issues below are ordered by
impact. Each item references the relevant lines in grammar.js and the
authoritative source.
All issues below have been implemented in grammar.js, regenerated into
src/, and covered by tests. The full suite (tree-sitter test) passes 30/30,
and all six query files compile against the grammar.
| Item | Status | Notes |
|---|---|---|
1.1 ^^→## (XOR) |
✅ Fixed | now ##; ^^ removed |
1.2 match→=~ |
✅ Fixed | now =~; match keyword removed |
| 1.3 Hex integers | ✅ Fixed | _INTCONSTANT accepts 0x… |
| 1.4 Real exponents | ✅ Fixed | _REALCONSTANT handles E, sign, leading dot; ad-hoc kludges removed |
1.5 Bare index/typename |
✅ Fixed | added to operator |
| 1.6 Empty file | ✅ Fixed | nedfile uses repeat |
| 1.7 Subgate/vector order | ✅ Fixed | gatepart = name subgate? (vector|++)? |
1.8 Bare extends |
✅ Fixed | removed the no-name alternative |
| 1.9 Multi-line strings | ✅ Fixed | \-newline continuation in _STRINGCONSTANT |
| 2 Operator precedence | ✅ Fixed | full PREC table; named binary_expression/unary_expression/conditional_expression/member_call/call/parenthesized_expression with left/right/operator/operand/condition/… fields |
3 field() coverage |
✅ Fixed | name/type/extends/implements/like_type/like_expr/condition/src/dest/arrow/value/volatile/direction/vector/param_name/from_value/to_value + expression fields |
| 4 Navigation queries | ✅ Added | tags.scm, locals.scm, injections.scm (xml into xmldoc), folds.scm, indents.scm; highlights.scm greatly expanded; literals exposed as named string/number/boolean/quantity/constant nodes |
| 5 Lexer fidelity | ✅ Mostly | unified '…'/"…" strings (retired _XMLCONSTANT); widened _PROPINDEX; UTF-8 BOM in extras; word: $._NAME for keyword/identifier separation. Property-value _COMMONCHAR left as-is (see note) |
| 6 Comment handling | ✅ Fixed | single comment token in extras (works everywhere); dropped _EMPTYLINE/inline_comment special-casing |
| 7 Test coverage | ✅ Added | test/corpus/Features covers operators, numbers, precedence, empty/comment-only files, multi-line/single-quoted strings, this/parent/sizeof/exists, channel interface, property decl, allowunconnected + inline channel like |
Deferred (documented): the property-value capture (_COMMONCHAR, §5) is
unchanged. A faithful fix requires an external scanner replicating ned2.lex's
stateful propertyvalue mode with paren-depth tracking; the current rule parses
all real-world corpus files correctly, so this was left as a known limitation
rather than risk a regression.
Also done as part of §4: channelinterfaceheader was made hidden
(_channelinterfaceheader) so its name field hoists onto channel_interface,
matching the other type headers.
These cause real files to misparse, which directly breaks any tool built on top.
grammar.js:572 defines XOR as ^^. NED has no ^^ operator. The real logical
XOR token is ## (ned2.lex:204: "##" → XOR, ned2.y:1549). Consequences:
a ## b(valid) fails to parse.a ^^ b(invalid) is wrongly accepted.
Fix: replace "^^" with "##" in the expression rule.
grammar.js:568 uses a match keyword. NED's pattern-match operator is =~
(ned2.lex:228: "=~" → MATCH, ned2.y:1545). There is no match keyword in
the lexer. Consequences:
a =~ "pattern"(valid) fails to parse.a match bparses, butmatchis actually just aNAMEin NED, so this is wrong.
Fix: replace "match" with "=~".
_INTCONSTANT is /\d+/ (grammar.js:734). The lexer also accepts hex:
0[xX]{X}+ (ned2.lex:129). So 0xFF, 0x1A2B fail to parse.
Fix: _INTCONSTANT: $ => /\d+|0[xX][0-9a-fA-F]+/.
The lexer real-number rules are (ned2.lex:24,130–131):
E [Ee][+-]?{D}+
{D}+{E} → REALCONSTANT e.g. 1e10, 2E+8
{D}*"."{D}+({E})? → REALCONSTANT e.g. .5, 1.5e-3, 2.5e8
The tree-sitter side (grammar.js:720–735) only has:
_REALCONSTANT: /\d+\.\d+/— no exponent at all,_intconstant_ext: /[0-9]+e[0-9]*/— lowercaseeonly, no sign, and allows a malformed empty exponent (1e).
Consequences — these valid literals misparse:
2.5e8,1.5e-3,6.022e23(real + exponent) →2.5parses,e8becomes aNAME.1E10,2e+8(uppercaseE/+sign) → fail.
This also breaks quantities like 1.5e-3s. The corpus only tests integer
exponents (LargeNet.ned … 2e8), masking the gap.
Fix: model numbers after the lexer, e.g.
_INTCONSTANT: $ => /\d+|0[xX][0-9a-fA-F]+/,
_REALCONSTANT: $ => /(\d+[eE][+-]?\d+)|(\d*\.\d+([eE][+-]?\d+)?)/,and drop the ad-hoc _intconstant_ext / .-kludge once the regex is correct.
operator (grammar.js:680) only models qname.index, qname.typename,
exists(...), sizeof(...). The authoritative operator rule
(ned2.y:1655–1662) also allows bare INDEX_ and TYPENAME:
operator : INDEX_ | TYPENAME | qname '.' INDEX_ | qname '.' TYPENAME | …
So a parameter value of just index or typename (common, e.g.
address = index; or = typename) fails to parse.
Fix: add "index" and "typename" as standalone alternatives in operator.
nedfile uses repeat1(...) (grammar.js:11), requiring at least one element.
The authoritative grammar allows an empty file (ned2.y:219–222: nedfile : definitions | %empty). An empty .ned file, or one containing only whitespace,
is a valid NED file but produces a parse error here.
Fix: use repeat(...) for the file body.
In gatepart (grammar.js:475–483) an index vector may appear before the
subgate, so gate[0]$i is accepted. NED requires the subgate first:
NAME opt_subgate vector (ned2.y:1343), i.e. gate$i[0]. The valid form still
parses, but the invalid order is also accepted (over-acceptance).
_inheritance (grammar.js:85–91) includes a lone "extends" alternative with
no type name, which is invalid NED. The authoritative opt_inheritance
(ned2.y:406–411) never allows extends without a name.
_STRINGCONSTANT: /"([^"\\]|\\.)*"/ (grammar.js:736). JS . does not match
newlines, but the NED lexer explicitly supports \<newline> line continuation
inside strings (ned2.lex:136). Multi-line string literals fail.
Fix: allow \\(.|\r?\n) in the escape branch.
Every binary and unary operator in _expression is wrapped in a single
prec.right(10, choice(...)) (grammar.js:542–586). This means the parse tree
does not reflect NED's operator precedence/associativity, which is fully
specified in ned2.y:51–69:
%right '?' ':' %left OR XOR AND EQ NE '<' '>' LE GE SPACESHIP
%left MATCH '|' '#' '&' SHIFT_LEFT SHIFT_RIGHT '+' '-' '*' '/' '%'
%right '^' %right UMIN_ NEG_ NOT_ %left '.'
Effects:
a + b * cparses as a flat / right-leaning tree instead ofa + (b * c).a - b - cis treated right-associative instead of left.- Any tool that evaluates, refactors, or even semantically highlights expressions gets the wrong structure.
For pure token coloring this is tolerable; for everything else it is a
significant limitation. Recommendation: introduce named precedence levels with
prec.left / prec.right mirroring the table above (this is the standard
tree-sitter idiom and removes the need for the catch-all prec.right(10)).
Only two fields are declared in the whole grammar (parameter_signature,
property_signature; grammar.js + src/node-types.json). Most structural
data is reachable only through alias()d anonymous-position nodes, which makes
queries brittle (position-dependent) and node-types.json uninformative for tool
authors.
Add fields for the elements tools navigate by, e.g.:
- module/channel/network/interface:
name,extends,like/implements. submodule:name,type,like_type,like_expr,vector/size,condition.connection:src,dest,arrow,channel,condition, loop bounds.parameter:type,name,value,is_volatile,is_default.gate:direction,name,vector.loop:param_name,from_value,to_value.
This is the single highest-leverage change for "tools can use this better."
queries/ contains only a sparse highlights.scm. To let editors and tools do
real work, add the standard tree-sitter query files:
tags.scm— symbol definitions/references (modules, channels, networks, interfaces, submodules, parameters, gates). Enables go-to-definition, symbol outline, and GitHub/stack-graphs-style navigation. This is what most "tools" actually consume.locals.scm— scopes forfor-loop variables, submodule names, gate names; enables correct rename and reference highlighting.injections.scm— injectxmlintoxmldoc("…")/ xml string params, and (optionally) treat@signal/@statisticproperty values specially.indents.scm/folds.scm— block-aware indentation and folding for the{ … }sections.
The existing highlights.scm should also be expanded (it currently colors only
a handful of keywords): cover import/export names, the type keywords
(double int string bool object xml), gate directions
(input output inout), operators, numeric/string/bool literals, @property
names, and the volatile/default/ask/this/parent/index/typename/
sizeof/exists/xmldoc words.
constreserved word: NED lexesconstas a keyword (ned2.lex:120, tokenCONST_). The grammar treats it as a plainNAME. (The token is unused inned2.y, so impact is small, but aconst-named identifier would behave differently than the reference parser.)- UTF-8 BOM: the reference lexer skips a leading BOM (
ned2.lex:230). The tree-sitter grammar does not; a BOM-prefixed file may fail on the first token. - Property-value capture uses
_COMMONCHAR: /[^"]/one char at a time (grammar.js:739,310–320). The reference lexer has a dedicatedpropertyvaluestate with paren/brace depth tracking (ned2.lex:174–183). The current approach is fragile around nested parens, embedded strings, and;/,/=that are significant only at depth 1; consider tightening it. - Property index character class:
_PROPINDEX(grammar.js:733) requires a leading letter/underscore, but the reference allows a leading*/?/digit (ned2.lex:169:({L}|{D}|[*?{}:.-])+), e.g.@foo[*]style indices. - Single vs. double quoted strings: the reference treats both
"…"and'…'uniformly asSTRINGCONSTANT(ned2.lex:133,143). The grammar splits them ad hoc between_STRINGCONSTANTand_XMLCONSTANT; unifying would simplify and match semantics. (CHARCONSTANTis declared but unused in the reference — no action needed.)
inline_comment is in extras (can appear anywhere), while the grouped
comment node (grammar.js:30) is only listed explicitly in a few contexts
(file top-level, params, gates, submodules, connections). _EMPTYLINE
(grammar.js:738) is also threaded into the top-level choice as if it were a
token. This dual scheme:
- can drop/misattach
commentnodes that appear in other positions (e.g. inside atypes:block, or between a submodule's braces), risking parse errors where the reference parser (which discards//comments entirely and re-attaches them by source position) would succeed; - duplicates concerns between
comment,inline_comment, and_EMPTYLINE.
The grammar's choice to surface comments as nodes is good for tooling (the
reference parser throws them away); the recommendation is just to make it
uniform — rely on extras for comments everywhere and drop the special-cased
comment/_EMPTYLINE placements, or document the intended attachment model.
The corpus (17 cases) covers real files well but misses the edge cases that hide the bugs above. Add focused cases for:
##(XOR) and=~(match) operators.- hex integers (
0xFF), exponent reals (2.5e8,1e+10,1.5e-3), exponent quantities (1.5e-3s). - bare
indexandtypenameas parameter values. - operator-precedence trees (
a + b * c,a ? b : c ? d : e,-2 ^ 2, chained&&/||/##). - empty file, comment-only file.
- multi-line (
\-continued) string literals. this./parent.qualified names,sizeof(...),exists(...).- channel
likeconnections andallowunconnected.
- Operator fixes
^^→##,match→=~(1.1, 1.2) — one-line, high impact. - Number lexer hex + real exponents (1.3, 1.4).
- Bare
index/typenameand empty file (1.5, 1.6). - Expression precedence (section 2) — large but enables semantic tooling.
field()coverage (section 3) andtags.scm/locals.scm(section 4) — the changes that most directly let tools "use this better."- Remaining lexer-fidelity, comment-model, and over-acceptance cleanups (1.7–1.9, sections 5–6), backed by new corpus tests (section 7).