Repository navigation
Your First Grammar
Let's build up an expression grammar piece by piece. By the end you'll have met most of ISPA's syntax. We'll go slowly and explain every symbol.
ID:
@ ([a-zA-Z_][a-zA-Z0-9_]*)
{@}
;
-
IDis written in UPPERCASE, so ISPA treats it as a token. The whole name must be uppercase, not just the first letter. - The pattern in the middle is a regular-expression-style match for identifiers.
-
@means "capture this." Everything the grammar captures can end up in the AST. -
{@}means "the AST node for this is whatever I captured." We captured one thing, so that one thing becomes the node. -
;ends the definition.
NUMBER:
@ [0-9]+ ('.' @ [0-9]+)?
@{decimal, floating}
;
Now there are two @s. Each one captures a piece, and the @{decimal, floating} block gives the captures names, in the order the @s appear. So decimal holds the digits before the dot, and floating holds the digits after it (the optional part).
These names end up as the field names of the AST node ISPA generates for the token, so they're worth choosing well.
Named captures follow the order of their @ captures in that production.
Lowercase names are rules. They build structure out of tokens (and other rules).
expr:
@ #logical
{@}
#logical:
@ compare (@ LOGICAL_OP @ compare)*
@{left, op, right}
;
#compare:
@ arithmetic (@ COMPARE_OP @ arithmetic)*
@{first, op, sequence}
;
#arithmetic:
@ term (@ PLUS | MINUS @ term)*
@{first, op, sequence}
;
#term:
@ value (@ MULTIPLE | DIVIDE | MODULO @ value)*
@{first, op, sequence}
;
#value:
@ STRING | NUMBER | ID
{@}
;
#group:
'(' @ expr ')'
{@}
;
;
There's a lot going on, so let's unpack it:
-
#name:defines a nested rule. Nested rules live inside their parent, right after the parent's own content and data block. They're scoped to the parent, sological,compareand friends belong toexpr. -
Nesting is how this grammar expresses operator precedence.
logicalis built fromcompare, which is built fromarithmetic, thenterm, thenvalue. Operators lower in the chain bind tighter. It's the classic technique, and nested rules keep it tidy and in one place. -
@ #logical {@}saysexpris a single capturedlogical, and that is its node. -
@{first, op, sequence}names three captures: the first operand, the operator, and the repeated operand(s).
That's a complete little expression language. 🎉
ℹ️ The
STRINGtoken in the README's example embeds a rule inside a token ('${' @ expr '}'). Rules inside tokens aren't supported yet, so treat that part as a preview of where things are going.
Run ISPA on your grammar (see Getting Started), then look at Generated Code to see the types you got, and Runtime API to see how to feed in text and get a tree back.