Foreign Bodies
Some things a grammar cannot say. Python’s indentation is the standard example: what counts as an indent depends on a stack of previous indents, which is not a shape any amount of EBNF will describe.
For those, an action’s body can live outside the grammar, in C you write.
Declaring one
_ as the body says “this action is defined elsewhere”:
indent = _;
dedent = _;
newline = _;
The action is declared, named and usable like any other. It simply has no grammar.
Binding it to your code
A foreign block says which function stands behind each one. The name on
the left is the action; the string on the right is what your program registers
the function under:
foreign {
indent: "apm_py_indent",
dedent: "apm_py_dedent",
newline: "apm_py_newline",
};
The actual binding happens at load time, and there are two ways to arrange it.
Linked in. Your program hands the machine a table of names and function pointers, and everything is one binary:
static const ApmForeignAction actions[] = {
{ "apm_py_indent", MyIndent },
{ "apm_py_dedent", MyDedent },
{ "apm_py_newline", MyNewline },
};
ApmVmForeignConfig fc = { actions, 3, MyInit, MyQuit, NULL };
cfg.foreign = &fc;
init and quit are optional and exist because a matcher that needs state –
an indent stack – has to be given somewhere to keep it. Whatever init
returns becomes the user pointer every call receives.
Loaded. Build the same file as a shared library and let the stock runner find the symbols itself:
apmr python.apmb file.py --plugin=python_foreign.dll
There is no plugin interface to implement: the foreign block already names a
symbol, so the runner reads those names out of the parser and looks each
one up. Two optional symbols, apm_plugin_init and apm_plugin_quit, do the
job of init and quit. One library may own that state; a second that exports
apm_plugin_init is refused rather than silently ignored.
The same .c file serves both ways, which is what
assets/samples/python_foreign.c does.
Using one
A foreign body is a matcher, exactly as char is. It reads text and says
how many bytes it took; it makes no node of its own. The action around it owns
whatever it matched, the same way it would over a literal:
name = <A:Za:z> . (<A:Za:z_0:9>)*;
block := name . ":" . indent . (name . newline)* . dedent;
Every call site stays an ordinary call, so nothing else in the grammar needs to know the body is not grammar.
Important
The placeholder declaration is required. _ is what tells the compiler the
action is deliberately bodiless rather than forgotten.
See also
foreign is a reserved word. The names inside the block
are quoted strings, so they are free of that restriction.