Config Settings
A grammar can ask the machine to build a different tree. One optional block, at the top level, with a setting per line:
config {
"ast-node-text" : FALSE,
"ast-node-char" : TRUE,
};
Every setting has a default, and what you do not write keeps it. An unknown key is an error, not a shrug.
A setting is either a switch, written TRUE or FALSE, or a size,
written as a number. A value of the wrong kind, or a size outside its range, is
refused when the grammar is compiled.
What can be set
setting |
kind |
default |
what it does |
|---|---|---|---|
|
switch |
|
an action becomes a node |
|
switch |
|
a matched text literal becomes a node |
|
switch |
|
|
|
switch |
|
adjacent text literals make one node |
|
switch |
|
a matched character literal becomes a node |
|
switch |
|
|
|
switch |
|
adjacent character literals make one node |
|
size |
|
bytes of nesting a positioned scope gets, one byte per open level. 8 to 4096 |
|
size |
|
slots in the run’s call cache, rounded up to a power of two. 8 to 1048576 |
A -counter or -series setting does nothing while the thing it counts is off,
and turning one on while the other is off is refused rather than ignored.
The two sizes
config {
"scope-ordered-buffer-size" : 128,
"cache-call-buffer-size" : 1024,
};
Neither changes what a grammar means. scope-ordered-buffer-size is how deep a
scope with positioned brackets may nest before the parse fails
saying so — one byte per open level, per ordered scope. cache-call-buffer-size
is how many results the run remembers so that the same rule at the same place is
not walked twice; more slots means fewer misses and more memory.
Write them when the language asks for it: a deeply nested document needs the first, a large grammar over a large file the second. The defaults are right for everything else.
Literals in the tree
kv := key ":" value;
With ast-node-text on — the default — port:8080 gives three children:
kv
key "port"
text ":"
value "8080"
The ":" node has no action to be named by, so it is named by what it
matched. That is forced rather than chosen: tex::icase("AND") matches and,
and tex::oneof("abc") matches one character out of three, so the literal in the
grammar would be the wrong label in both cases.
Turn it off and the literal matches and consumes exactly as before, silently:
config { "ast-node-text" : FALSE };
One node, not many
Adjacent literals say one thing, so they make one node:
z := "A" "B"; -> text "AB"
z := tex::oneof("abc")-5; -> text "aabcb"
z := "A" "C"-5 "D"; -> text "ACCCCCD"
A . between two literals ends the run — the grammar separated them, and
whether the input happened to put nothing between them is not the grammar’s
business:
z := "a" . "b"; -> text "a", text "b"
So does anything that is not a literal:
z := "a" w "c"; -> text "a", w, text "c"
Parsing with no tree at all
Turn everything off and nothing is built. The parse still says what it matched and how far it got, which is all a recogniser needs:
config { "ast-node-action" : FALSE, "ast-node-text" : FALSE };
With actions off but literals on, the levels go and the literals stay.
Warning
Changing a setting changes the AST — that is what it is for. An AST interpreter is written against one config, so changing one is a breaking change for whatever reads the tree.
Note
Optimized and unoptimized builds produce identical trees, whatever the config. The optimizer declines any rewrite it cannot make invisible.