This directory does not contain grammar files. The parser in internal/core/parser/ is hand-written recursive descent.
For reference, the official Xtext grammar files from OMG are available at:
KerML Grammar:
SysML v2 Grammar:
These files are licensed under the Eclipse Public License 2.0 (EPL-2.0), so they are not included in this repository. When you need them, ./scripts/download-pilot-grammars.sh fetches them at the pinned release into build/pilot-grammars/.
grammar-coverage.md reports which grammar productions the project's test inputs exercise. It measures whether an input for each production is present, not whether the parser code for it ran.
conformance-audit.md goes through every keyword and construct OpenSysML accepts and sorts it into one of three groups: standard notation (accepted silently), OpenSysML extensions (accepted with a nonstandard-notation warning), and KerML notation used in a .sysml file (accepted with a kerml-notation warning). Each entry cites the file:line in the pinned OMG grammar that justifies it.
The OMG textual notation covers state definitions and usages, entry/do/exit
subactions, and transitions (SysML.xtext, StateDefinition through TransitionUsage,
near the /* STATES */ section). That is all it says about state machines: there
is no production for any kind of pseudostate, and none for event deferral.
Some of these concepts do have semantics in the bundled KerML semantic
library or the Systems Library, and where they do, that library is the governing reference.
For the rest we cite UML 2.5.1 §14.2.3.4 (Pseudostates), but UML's notation for them is
diagrammatic, so there is no textual syntax to borrow.
OpenSysML therefore defines its own keywords for them, valid only inside a state body.
They are a documented extension, not OMG notation, and using one produces a
nonstandard-notation warning:
| Form | Meaning | Semantic reference |
|---|---|---|
choice <name>; |
dynamic conditional branch | KerML ControlPerformances::DecisionPerformance — selects one of the successions leaving it, outgoingHBLink: HappensBefore[1] (notation is an OpenSysML invention) |
junction <name>; |
static branch/merge | KerML DecisionPerformance::outgoingHBLink[1] / MergePerformance::incomingHBLink[1] (notation is an OpenSysML invention) |
fork <name>; |
parallel split | UML fork pseudostate (a state-body fork has no SysML v2 or KerML counterpart; the action-level one is Actions::ForkAction) |
join <name>; |
parallel synchronization | UML join pseudostate (a state-body join has no SysML v2 or KerML counterpart; the action-level one is Actions::JoinAction) |
history <name>; |
shallow history (UML H) |
UML shallowHistory pseudostate |
shallow history <name>; |
shallow history, spelled out | UML shallowHistory pseudostate |
deep history <name>; |
deep history (UML H*) |
UML deepHistory pseudostate |
entry point <name>; |
entry point | UML entryPoint pseudostate |
exit point <name>; |
exit point | UML exitPoint pseudostate |
defer <event> [, <event>]*; |
events the state retains while active | KerML StatePerformances::StatePerformance::deferrable: Transfer[0..*] subsets acceptable — "transfers … can be considered for acceptance more than once"; dispatch order is Occurrences::Occurrence::incomingTransferSort, defaulting to earlierFirstIncomingTransferSort |
The action-level fork and join control nodes are SysML v2's Actions::ForkAction
and JoinAction. The library gives them no behavior of their own (a
ControlAction has "no inherent behavior"); their effect "results from requiring that the target
[respectively source] multiplicity of all outgoing [incoming] succession
connectors be 1..1".
Notes:
forkandjoinare the exception in the table above. Both are action node literals that a state body already admits (SysML.xtext:1684,:1678,:1761-1763), so they count as standard and are not warned about.- None of
choice,decision,deep,defer,done,final,history,initial,junctionorshallowis a reserved word. None of them appears as a literal in the pinned grammars, so they remain ordinary names and are recognized only in the positions where the notation above needs them. pointis not reserved either. It is recognized only afterentryorexit, and only when a pseudostate name and;follow, because models routinely declare features namedpoint.entry <action>keeps its OMG meaning.onandvarare not reserved, for the same reason and on the pilot implementation's authority:onis not a literal in any of its grammars, andvarappears only inKerML.xtext'sBasicFeaturePrefix(isVariable ?= 'var'). Sostate on { … },then on;(the OMG training corpus writes both) andattribute var : Integer;all declare and name features, whilevarbefore a kind keyword (var feature x,var attribute total : Integer;) still marks a variable feature.varwithout a kind keyword is not supported and is reported. See pilot-differential.md.- A deferred event is parsed exactly like a transition trigger, so both a signal
name (
defer Ping;) and a call event (defer setSpeed(value);) are accepted. Time and change events cannot be deferred; lowering reports them. deferis only meaningful inside a state. One written in the machine's own body is reported bylower.ToStateGraph.- Unreserved does not mean invisible to editors.
lexer.ContextualWords(kind)lists these words for the two places that want them, the VS Code grammars (keywords-contextual) and LSP keyword completion, without the lexer reserving any of them.varis in the.kermllist only, andonis in neither, since it is never syntax. The two lists are checked to be disjoint when the grammars are generated, so listing a word cannot make it reserved.
Grammar conformance is validated by parsing OMG's own files:
-
Stdlib conformance gate - all 98 bundled library files (94 OMG standard library files and 4 OpenSysML extensions) must parse with zero diagnostics
- See:
internal/core/libs/stdlib_conformance_test.go - These files are the source of truth for correct parsing
- See:
-
Training examples - 100 OMG training files (the current result is on the page below)
- See:
docs/project/training-examples.md
- See:
-
Golden AST tests - 33 fixtures with expected AST output
- See:
internal/core/parser/testdata/parse/
- See:
-
Negative tests - 36 test cases for error recovery
- See:
internal/core/parser/negative_test.go
- See:
The parser is hand-written rather than generated because that gives us:
- Performance - 10-100x faster than generated parsers
- Error recovery - custom
ErrorNodeinsertion for fault tolerance - Control - full control over diagnostic messages
- Incremental parsing - a path to future LSP support