Repository navigation
Grammar Syntax
The reference page. If you'd rather learn by example, start with Your First Grammar.
| Kind | How to spot it | What it's for |
|---|---|---|
| Token | Name is entirely UPPERCASE (NUMBER, LOGICAL_OP) |
Lexer-level pieces: identifiers, numbers, operators |
| Rule | Lowercase name (expr) |
Parser-level structure built from tokens and other rules |
| Nested rule | Written as #name: inside another rule |
A rule scoped to its parent |
Every definition ends with ;.
🚧 Not yet supported: using a rule inside a token. This is planned, see Roadmap and Status.
@ marks the part of a production you want to capture. Anything you don't mark isn't captured, which keeps your AST small and your lexer fast.
NUMBER:
@ [0-9]+ ('.' @ [0-9]+)?
@{decimal, floating}
;
{@} assigns one single AST node to the production. The value can be either:
-
@, meaning the first (and only) thing you captured, or - a Python-style expression.
ID:
@ ([a-zA-Z_][a-zA-Z0-9_]*)
{@}
;
When a production captures several things, @{a, b, c} names them. Put @ before each element you want, then list the names in the block. The names become the fields of the generated AST node.
#compare:
@ arithmetic (@ COMPARE_OP @ arithmetic)*
@{first, op, sequence}
;
Note: @name will produce an ast node name with no control directed to @{}. @ name will direct. Single space matters here.
#name: defines a rule that lives inside another rule. A nested rule can be regular or advanced.
A regular (non-advanced) rule can, in turn, contain more rules inside it. These go right after the rule's content, followed by its data block or its fail block.
Nesting is handy for things like precedence levels, where every level is a small rule that belongs to one parent. See the full expr example in Your First Grammar.
Advanced rules use braces and executable clauses. For example, COMMENT { match '#' [^\n\0]*; ctx.skip() } recognizes a comment and tells the lexer to discard it. See examples/yaml/grammar.isc for indentation handling with ctx.emit, shared state, and ctx.error. The embedded action language is a supported Python subset, not arbitrary Python.
| Syntax | Meaning |
|---|---|
'text' |
A literal string |
[0-9], [^"]
|
Character classes (and negated classes) |
( ... ) |
Grouping |
*, +, ?
|
Zero or more, one or more, optional |
| |
Alternation |
Grammars can include Python-style pseudocode for semantic actions. This is what makes context-sensitive lexing possible.
🚧 LR/LALR accept advanced rules containing ordinary
matchclauses, but reject embedded Python actions and conditional matches. LR(*) currently rejects builds. See Algorithms and Limitations.
For grammar modules and external parser imports, see Modules and External Parsers. See Counted Repetition, Modifiers, Templates And Inheritance, and Lexer Semantic Builder for their current syntax and behavior.
Fail blocks define custom error messages and recovery. They're still limited, so they have their own page: Error Recovery.