Quick Reference
A one-page summary of every APML feature. Each entry links to its full chapter.
Module — details
program MyParser; # name the module
program "algodal" json; # ... in two parts; the full name is algodaljson
Actions — details
a = "x"; # charseq action (=) -> flat characters
b := "x"; # syntac action (:=) -> AST node
Series, Options, Groups — details
s = A B C; # series: A then B then C
o = A | B; # option (OR): every alternative checked, longest wins
f = A / B; # option (Firstly OR): stop at first match
g = A (B | C) D; # group as a single unit
Alias — details
alias tab \x09; # a name for a literal value
Characters — details
A = char; # any single utf-8 character
B = <A:Z>; # character block; ranges use ':'
C = \x41; # hex literal (A)
D = \u0041; # unicode literal (A)
E = \x43,41,54; # chain -> "CAT"
F = \x41:5A; # range A..Z
Counters — details
<A:Z>+; # one or more
<A:Z>*; # zero or more
<A:Z>?; # zero or one (optional)
<A:Z>-5; # exactly 5
<A:Z>-5+; # five or more
<A:Z>-7:12; # between 7 and 12
Built-in Actions — details
spc; # space or tab
nl; # line break
eol; # end-of-line (a line break or end of input)
eof; # end of input (matches zero width)
Inbetween (skip) — details
stmt = "(" . "A" . ")"; # '.' skips per the config below
. { spc, nl }; # required whenever '.' is used
Stopping a counter — details
char*::until("-->") # repeat until "-->", and take it too
(char* ^ "-->") # repeat until "-->", and leave it for the next unit
A counter followed by its own base needs neither — A+ A is compiled as
A A+.
Permutation — details
A = perm["A" "B" "C"]; # match members in any order, each once
Text Functions — details
tex::order("ABC"); # same length, any character order
tex::oneof("ABC"); # one character from the set
tex::icase("ABC"); # case-insensitive exact match
tex::icase(word); # ...over a texvar, or a semvar's members
Result Functions — details
A predicate asks a question about the matched text and can only pass or fail it. A producer answers with a number instead, and never fails anything.
# predicates
name::is("Fred"); # matched text equals
name::not("Amber"); # ... does not equal
name::subkind("Fr"); # ... contains
# narrowing what is asked about
name::part(1); # 1-indexed character
name::part(1:4); # characters 1..4
name::part(2+); # character 2 to the end
name::part(1)::is("F"); # chained
# producers -- no parentheses, they take no argument
name::char_count; # characters matched
name::to_num; # matched text as a number; 0 if it is not one
A+::iter_steps; # times a counter ran; 1 if not counted, 0 if none
# a producer may be compared against a NUMBER
name::char_count::is(3); # exactly three characters
name::to_num::is(200); # the value 200 -- so "0200" matches too
name::char_count::is(limit); # ... or against a numvar
Variables — details
Local to one action, or global to the whole parse. A texvar holds a span of
text; a numvar holds a number.
texvar greeting = "Hello"; # global, set before parsing starts
numvar limit = 3; # global number
A := (word => texvar x) x; # capture, then require the same text again
B := (word => texvar x) (word => x); # declare once, assign again
C := (n::to_num => numvar v); # a producer fills a numvar
A variable must be assigned on every path that reaches a read, or the grammar is refused — nothing is zeroed, so there is no safe stale value.
Semvar — details
A set of text the grammar builds as it parses and then matches against — what a semantic predicate needs and a plain variable cannot give. Global, at most four.
semvar kind = "int", "short"; # declared members
semvar name; # starts empty
decl := "typedef" (ident => kind) ";"; # add what was parsed
use := kind; # match any member, longest first
alt := kind::first; # ... earliest added instead
soon := try kind [ident] ";"; # read it before it is declared
try set [unit] matches the bracketed unit — the replacement matcher — and
promises that text will be declared into set before the input ends. A promise
still owed when the input runs out is an error. One semvar, one unit.
Scope — details
A depth the input moves. Leaving a scope forgets whatever was added to the sets bound to it — ordinary block scoping.
scope blk
begin = "{";
end = "}";
feat {"scope": blk} semvar kind = "int"; # bound; an unbound set never forgets
block := blk::begin item* blk::end;
begin and end may sit in different actions, and blocks nest freely.
Written as lists, the brackets pair by position — whatever opened a level is
the only thing that closes it, so { ... ) does not close. Two to four
positions, and the two lists must be the same length:
scope blk
begin = ["{", "("];
end = ["}", ")"];
Logic Blocks — details
A logic block tests values. It reads no input and moves no cursor: it
succeeds or fails, and consumes nothing either way. {{ ... }} is final — it
runs for what it does, always holds, and nothing may follow it in the grammar.
A := (word => texvar x) {x == "cat"}; # a test
B := "a" {{9 => n}}; # a deed, always holds
Operators, in C’s precedence, tightest first:
NOT a # negation
a * b a / b # division by zero is 0
a + b a - b # a - b clamps at 0
a < b a <= b a > b a >= b
a == b a != b # text compares by bytes; == and != are all text has
a AND b # short-circuits: b is not evaluated when a is false
a OR b # short-circuits: b is not evaluated when a is true
TRUE FALSE # one and zero
set::is(t) set::not(t) set::clear # questions about a semvar
error("...") # stop the whole parse
Statements are separated by ;, and the last one’s value is the block’s answer.
A statement that produced nothing — an assignment, a cleared set — is a deed
rather than an answer, and a deed is no reason to fail.
error("...")
Every other failure means “this did not match here”, and it sends the machine
looking for another reading. error() means the input is wrong and there is
nothing else to try: it travels straight out of options, series, counters,
permutations and if conditions alike, ends the run, and its message becomes
the run’s error.
z := (word => texvar x) {x == "cat" OR error("only cats here")};
Because OR short-circuits, that reports nothing for a cat and stops for a dog.
{{ }} does not absorb it either — always holding is an answer, and error()
does not give one.
IF Statements — details
if (cond) [then | else] # both branches
if (cond) [then] # then only
if (cond) [| else] # else only
The condition may be a logic block or a grammar; a grammar condition consumes
what it matched. With no branch to take, an if is like E? — the cursor
resets and the next unit is read.
Feature Config
How a declaration is configured, written apart from what it declares. Entries are keyed, so order never matters; a key the feature does not read is an error.
{ "key" : value , ... } # value is a label, a number or a string
Config — details
What the grammar asks the machine to build. Optional; every setting has a default and what you do not write keeps it.
config {
"ast-node-text" : FALSE, # literals stop being nodes
"ast-node-char" : TRUE, # char literals start being nodes
};
setting |
default |
|---|---|
|
|
|
|
|
|
A matched literal is a node named by what it matched, and adjacent literals
make one node — "A" "B" is text "AB". A . between them ends the run, so
"a" . "b" stays two. Turn everything off and a parse builds no tree at all,
which is all a recogniser needs.
Changing a setting changes the AST. An interpreter is written against one config.
Parser Block — details
parser {
main_grammar; # the single start grammar; the loop re-runs it over what remains
};
parser { A | B | C; }; # an option
parser { A B C; }; # a series
parser { item; }; # the loop walks adjacent items
parser { item (. item)*; }; # items separated by the inbetween (`.`)
Node IDs — details
node_id {
Label: "saved name", # what an action is saved as in the AST
};
Linking — details
link p2; # a separate machine, called into
link "algodal" json; # sugar for `link algodaljson;`
X := p2::value; # always qualified
Binding Power — details
bindpow bp {
"+" : (50, 51) ; # r = l + 1 -> left-associative
"^" : (11, 10) ; # r = l - 1 -> right-associative
"not" : (0, 70) ; # (0, r) prefix; (l, 0) postfix
};
feat {"bind": bp} expr := atom | expr . "+" . expr | "not" . expr;
Foreign Bodiess — details
indent = _; # body lives in your C code
foreign { indent: "apm_py_indent" };
AST Maps — details
(A . B) -> (B A) # order in the map is order in the tree
(A . B) -> (A: (B)) # A becomes B's parent
(A . B) -> (A) # B is discarded
(A . B) -> (A [B]) # B's children take B's place
(A => texvar v) -> (A node("Extra", v)) # a node nothing matched
('a'A . 'b'A) -> ('b' 'a') # labels tell two apart
System Functions — details
error("only cats here") # stop the parse; nothing else is tried
Keywords — details
Every word the language spells out is reserved and cannot be a declared name. Config and feature keys are quoted strings, so they are exempt.
How to Use — details
apma mylang.apm -o mylang.apmb
ApmBinary program = ApmReadBinaryFile("mylang.apmb", &ok);
ApmVmResult result = ApmVmRun(&program, config);
Comments — details
# line comment to end of line