Phoenix

Phoenix  /  Phoenix cheatsheet

Phoenix cheatsheet

One page. manual.md explains, reference.md is exhaustive, tutorial-picture.md starts from nothing.

Command line

phx desc.phx                  read the description, check it, print it back
phx desc.phx prog.src         run the default driver over prog.src
phx desc.phx prog.src --driver run     a driver by name
phx desc.phx -o desc.c        write the description out as a C program
   
--tree the tree, whatever drivers exist
--tokens the token stream, and stop
--nodes the node types the grammar builds, and their fields
--drivers what there is to choose from
--imports the files the description was assembled from
--grammar the grammar as it was understood
--run PASS run one %pass on its own
--show ATTR print this attribute of the root instead of the driver’s
--raw write the answer with no trailing newline (binary targets)
--stats how much work reading the source took
--quiet say nothing on success; the exit status is the answer
-I DIR where to look for a file the source includes. Repeatable
--no-includes do not follow %include; leave the include nodes in

Exit status: 0 fine, 1 the description or the program was refused, 2 the command line was.

The shape of a file

(* a comment *)
%import "lexical.phx" .          (* modules, merged into one grammar *)

%tokens .                        (* the default; characters to tokens *)
%fragment letter digit .         (* helpers, never tokens on their own *)
%skip space comment .            (* produced, then thrown away *)
%ignorecase .                    (* letters compare without regard to case *)

%syntax .                        (* from here, matched over tokens *)
%start program .                 (* the goal rule; the first if unsaid *)

%pass name       ... clauses ...
%rewrite name strategy  ... rules ...
%driver name = pass, pass -> attr .

Grammar

   
a = b c . a production; =, := and ::= all define, . optional
a \| b ordered choice — b is tried only if a failed
[ a ] optional. Its value is a list: empty, or what it matched
{ a } repetition. Its value is a list, flattening every iteration
( a ) grouping
"lit" a literal
"a" .. "z" a character range — lexical only
! a one character, provided a does not match here — lexical only
l:factor a label, read back as $l

Escapes inside a literal: \" \\ \n \t \r \0 \' \xHH.

Actions

statement = "print" e:expression ";"   -> Print(value: $e)
          | "(" expression ")"         -> $2 .
expression = term { "+" term -> Binary(op: $1, left: $$, right: $2) } .
   
$1 the n’th factor, from one, counting everything including literals
$name a labelled factor
$$ what has been built so far — mentioning it makes the action a left fold
$pos where this node came from: Position(line, column, file, endline, endcolumn)

No -> at all: a body that produced one value answers that value; any other number answers a node named after the rule, holding them.

Passes

%pass typecheck
  thread env = empty                   (* declared before it is updated *)
  otherwise type = "void"              (* what a node with no clause answers *)

  Variable : type = lookup($env, $name)
           ! not defined($env, $name) : "'{}' is not defined" of $name .

  Let      : env  = bind($env, $name, $value.type)
           : type = "void" .

  Block    : down indent = "{}    " of $indent .
   
: attr = e synthesised — leaving, after the children
: down attr = e inherited — entering, before the children, visible below
: attr = e where attr is threaded threaded — leaving, and it flows on
! cond : message a check. It runs before the attributes it guards
otherwise attr = e for every node whose own rule answers nothing for attr
thread attr = e declares a thread and its starting value
down on a threaded attribute sets the thread for the subtree — this is how a thread nests

$child.attr reads a child’s attribute; over a list it means that of each. A node’s field is read before an attribute of the same name.

Rewrites

%rewrite fold bottomup
  Binary(op: "+", left: Number(text: a), right: Number(text: b))
    => Number(text: text(int($a) + int($b))) .

bottomup children first · topdown this node first · innermost bottom-up, again until nothing matches. A rewrite sees its bindings, the matched node’s fields and $pos — never an attribute.

Patterns

   
Binary a node of that type
Binary(op: "+", left: a) a field written with a value tests it, with a name binds it, left out is not looked at
[a, b], [] a list of exactly that many
"text", 45, true, nil that value
name anything, bound to that name
_ anything

First match wins. A general pattern above a specific one is refused.

Expressions

Precedence, tightest first:

f(x)  $a.b          call, attribute
not  -              unary
*  /  div  mod
+  -
=  <>  <  >  <=  >=
and                 short-circuits
or                  short-circuits
of                  formatting, loosest

if c then a else b works out only the arm it takes; c must be a boolean and the else is required.

Six kinds of value: integer (64-bit, traps on overflow), float (IEEE 754), text (bytes, one-based, both ends inclusive), boolean, nil, node and list. 1 + 1.0 is an error — nothing converts implicitly. div and mod are floored; quotient and remainder truncate. = and <> compare structurally across any two values; < > <= >= only within a kind.

"a {} b {}" of x, y fills the holes left to right. {{ and }} are literal braces. A nil, a node or a list has no written form.

The library

   
empty the empty environment (a bare lower-case name is a call with no arguments)
bind(env, name, value) a new environment. name may be a list of names
lookup(env, name), lookup(env, name, default) nil when absent, or the default
defined(env, name) a boolean
positions(list) [value, index] pairs — zero-based, first occurrence wins
int(t), int(t, base), float(x), text(x) conversions
floor(f), ceiling(f), round(f), truncate(f) float to integer, named
quotient(a, b), remainder(a, b) division that truncates, as C does
bitand(a, b), bitor(a, b), bitxor(a, b) the bits, on 64-bit two’s complement
size(x) bytes of text, elements of a list, fields of a node
sizes(list) the same for each element
at(list, i) one-based
slice(t, from, to) one-based, both ends included
split(t, sep) a list of text
find(t, part) where part first begins, from one; 0 when absent
join(list), join(list, between) text
flatten(list) one level
repeat(x, n) text, or a list’s elements, n times over
each(list, template), each(a, b, template) the template’s {} is the element; two lists run in step, to the longer
bytes(n, width) 1–8 bytes, little-endian. A list of numbers gives a list of encodings

Directives

%tokens %syntax %fragment %skip %start %ignorecase %import %embed %require %include %define %pass %rewrite %driver

%import "expression.phx" .       beside this file, then in lib/
%embed  runtime "rt.c" .         the file's bytes, under a name
%define twice(n) = $n * 2 .      a named computation; sees only its parameters
%require primary .               a hole this module leaves open
%include Include path .          which node is an include, and which field is the file
%driver c = show, typecheck, emit-c -> out .

A driver with no -> is a validation run: the exit status is all it says.