]> ocean-lang.org Git - ocean/blobdiff - csrc/oceani.mdc
Oceani - Cataract Creek version
[ocean] / csrc / oceani.mdc
index c4f7e2998c04d7ee475b365b16c1e0d2bcf30793..98430c21e93b1951f7ce7a80f26338c2ab7a82ca 100644 (file)
@@ -1,8 +1,8 @@
-# Ocean Interpreter - Stoney Creek version
+# Ocean Interpreter - Cataract Creek version
 
-Ocean is intended to be an compiled language, so this interpreter is
+Ocean is intended to be a compiled language, so this interpreter is
 not targeted at being the final product.  It is, rather, an intermediate
-stage, and fills that role in two distinct ways.
+stage and fills that role in two distinct ways.
 
 Firstly, it exists as a platform to experiment with the early language
 design.  An interpreter is easy to write and easy to get working, so
@@ -29,42 +29,55 @@ be.
 
 ## Current version
 
-This second version of the interpreter exists to test out the
-structured statement providing conditions and iteration, and simple
-variable scoping.  Clearly we need some minimal other functionality so
-that values can be tested and instructions iterated over.  All that
-functionality is clearly not normative at this stage (not that
-anything is **really** normative yet) and will change, so early test
-code will certainly break in later versions.
-
-The under-test parts of the language are:
-
- - conditional/looping structured statements
- - the `use` statement which is needed for that
- - Variable binding using ":=" and "::=", and assignment using "=".
-
-Elements which are present to make a usable language are:
-
- - "blocks" of multiple statements.
- - `pass`: a statement which does nothing.
- - expressions: `+`, `-`, `*`, `/` can apply to numbers and `++` can
-   catenate strings.  `and`, `or`, `not` manipulate Booleans, and
-   normal comparison operators can work on all three types.
- - `print`: will print the values in a list of expressions.
- - `program`: is given a list of identifiers to initialize from
-   arguments.
+This fourth version of the interpreter showcases the latest iteration of
+the design for parsing indents and line breaks, provides a first cut of
+support for references and functions, and introduces some syntax for a
+array with run-time length - currently only used for "argv".
+
+Indents are now explicit elements of the grammar and we no longer
+require a colon before an indent.  The colon still appears on "if" and
+"while" statements and others but it now marks the end of the
+expression.  Where there is no expression, such as after "else", there
+is no colon - an indent can immediately follow the "else".
+
+References can refer objects of any type which has a name, and this
+explicitly excludes other pointers and arrays.  This makes a very clear
+difference between references and things that they refer to, which we
+will see makes the description of function parameters simpler.  As a
+structure can hold a reference it is still quite possible to have a
+reference to a reference, but there will be a structure which keeps the
+inner and outer clearly separate.
+
+Functions can receive by-value or by-reference parameters with by-ref being
+declared like references.  If a non-reference is passed to a reference
+parameter, it is passed by-reference.  Functions can return a single
+value, or can return a collections of values which acts like a structure.
+
+The only IO that is currently possible is that "input" can be received
+in the sense that command line arguments are available to `main()`, and
+"output" can be generated with the "print" statement.  It is quite
+possible that neither of these will remain in the final language.
+
+The current scalar types are "number", "Boolean", "string" and the new
+"reference".  Boolean will likely stay in its current form, "string" and
+"number" are still open to being revised.  Compound types are structures
+and arrays.
 
 ## Naming
 
 Versions of the interpreter which obviously do not support a complete
-language will be named after creeks and streams.  This one is Stoney
+language will be named after creeks and streams.  This one is Cataract
 Creek.
 
-Once we have something reasonably resembling a complete language, the
-names of rivers will be used.
+Once we have support for methods, the names of rivers will be used.
+With semantic analysis can start tracking changes to effective types
+typing within the code (e.g.  a ref becoming "known to not be NULL"),
+names of lakes will be used.
+
 Early versions of the compiler will be named after seas.  Major
 releases of the compiler will be named after oceans.  Hopefully I will
-be finished once I get to the Pacific Ocean release.
+be finished once I get to the Pacific Ocean release - otherwise I might
+need to use Lunar Maria.
 
 ## Outline
 
@@ -73,20 +86,33 @@ out the program from the parsed internal structure.  This is useful
 for validating the parsing.
 So the main requirements of the interpreter are:
 
-- Parse the program, possibly with tracing
-- Analyse the parsed program to ensure consistency
-- print the program
-- execute the program
+- Parse the program, possibly with tracing,
+- Analyse the parsed program to ensure consistency and deduce implicit
+  information.
+- Print the program,
+- Execute the "main" function in the program, if no parsing or
+  consistency errors were found.
 
 This is all performed by a single C program extracted with
-`parsergen`.
+`parsergen`, using the `scanner` library.
+
+There will be two formats for printing the program: a default that uses
+indenting to show structure and an alternate that uses bracketing.  So a
+`--bracket` command line option is needed for that.
+
+The program appear in an "`mdcode`" file and is normally the first
+top-level code section found.  However an alternate section can
+be requested so that a file (such as this one) can contain multiple
+programs.  This is effected with the `--section` option.
+
+This code must be compiled with `-fplan9-extensions` so that anonymous
+structures can be used.
 
-There will be two formats for printing the program: a default and one
-that uses bracketing.  So a `--bracket` command line option is needed
-for that.  Normally the first code section found is used, however an
-alternate section can be requested so that a file (such as this one)
-can contain multiple programs This is effected with the `--section`
-option.
+The information gathered while parsing, and used while executing, is all
+stored in a single `parse_context` data structure.  And this exposition
+of the program progresses we will add various fields to this structure.
+It will be pass to many function, and all reduction code (called by the
+parsing engine) will have easy access to it.
 
 ###### File: oceani.mk
 
@@ -106,7 +132,9 @@ option.
 
 ###### Parser: header
        ## macros
+       struct parse_context;
        ## ast
+       ## ast late
        struct parse_context {
                struct token_config config;
                char *file_name;
@@ -123,8 +151,11 @@ option.
        #define config2context(_conf) container_of(_conf, struct parse_context, \
                config)
 
-###### Parser: code
+###### Parser: reduce
+       struct parse_context *c = config2context(config);
 
+###### Parser: code
+       #define _GNU_SOURCE
        #include <unistd.h>
        #include <stdlib.h>
        #include <fcntl.h>
@@ -148,8 +179,8 @@ option.
        ## core functions
 
        #include <getopt.h>
-       static char Usage[] = "Usage: oceani --trace --print --noexec --brackets"
-                             "--section=SectionName prog.ocn\n";
+       static char Usage[] =
+               "Usage: oceani --trace --print --noexec --brackets --section=SectionName prog.ocn\n";
        static const struct option long_options[] = {
                {"trace",     0, NULL, 't'},
                {"print",     0, NULL, 'p'},
@@ -159,24 +190,28 @@ option.
                {NULL,        0, NULL, 0},
        };
        const char *options = "tpnbs";
+
+       /* pr_err() is used to report inconsistencies in the mdcode,
+        * particularly missing or duplicate section names.
+        */
+       static void pr_err(char *msg)                   // NOTEST
+       {
+               fprintf(stderr, "%s\n", msg);           // NOTEST
+       }                                               // NOTEST
+
        int main(int argc, char *argv[])
        {
                int fd;
                int len;
                char *file;
-               struct section *s;
+               struct section *s = NULL, *ss;
                char *section = NULL;
                struct parse_context context = {
                        .config = {
-                               .ignored = (1 << TK_line_comment)
-                                        | (1 << TK_block_comment),
-                               .number_chars = ".,_+-",
-                               .word_start = "_",
-                               .word_cont = "_",
+                               ## scanner configuration
                        },
                };
                int doprint=0, dotrace=0, doexec=1, brackets=0;
-               struct exec **prog;
                int opt;
                while ((opt = getopt_long(argc, argv, options, long_options, NULL))
                       != -1) {
@@ -202,117 +237,142 @@ option.
                context.file_name = argv[optind];
                len = lseek(fd, 0, 2);
                file = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
-               s = code_extract(file, file+len, NULL);
+               s = code_extract(file, file+len, pr_err);
                if (!s) {
                        fprintf(stderr, "oceani: could not find any code in %s\n",
                                argv[optind]);
                        exit(1);
                }
+
+               ## context initialization
+
                if (section) {
-                       struct section *ss;
                        for (ss = s; ss; ss = ss->next) {
                                struct text sec = ss->section;
                                if (sec.len == strlen(section) &&
                                    strncmp(sec.txt, section, sec.len) == 0)
                                        break;
                        }
-                       if (ss)
-                               prog = parse_oceani(ss->code, &context.config,
-                                                   dotrace ? stderr : NULL);
-                       else {
+                       if (!ss) {
                                fprintf(stderr, "oceani: cannot find section %s\n",
                                        section);
-                               exit(1);
+                               goto cleanup;
                        }
                } else
-                       prog = parse_oceani(s->code, &context.config,
-                                   dotrace ? stderr : NULL);
-               if (!prog) {
-                       fprintf(stderr, "oceani: fatal parser error.\n");
-                       context.parse_error = 1;
-               }
-               if (prog && doprint)
-                       print_exec(*prog, 0, brackets);
-               if (prog && doexec && !context.parse_error) {
-                       if (!analyse_prog(*prog, &context)) {
-                               fprintf(stderr, "oceani: type error in program - not running.\n");
-                               exit(1);
-                       }
-                       interp_prog(*prog, argv+optind+1);
+                       ss = s;                         // NOTEST
+               if (!ss->code) {
+                       fprintf(stderr, "oceani: no code found in requested section\n");        // NOTEST
+                       goto cleanup;                   // NOTEST
+               }
+
+               parse_oceani(ss->code, &context.config, dotrace ? stderr : NULL);
+
+               resolve_consts(&context);
+               prepare_types(&context);
+               if (!context.parse_error && !analyse_funcs(&context)) {
+                       fprintf(stderr, "oceani: type error in program - not running.\n");
+                       context.parse_error += 1;
                }
-               if (prog) {
-                       free_exec(*prog);
-                       free(prog);
+
+               if (doprint) {
+                       ## print const decls
+                       ## print type decls
+                       ## print func decls
                }
+               if (doexec && !context.parse_error)
+                       interp_main(&context, argc - optind, argv + optind);
+       cleanup:
                while (s) {
                        struct section *t = s->next;
                        code_free(s->code);
                        free(s);
                        s = t;
                }
-               ## free context vars
+               // FIXME parser should pop scope even on error
+               while (context.scope_depth > 0)
+                       scope_pop(&context);
+               ## free global vars
+               ## free const decls
                ## free context types
+               ## free context storage
                exit(context.parse_error ? 1 : 0);
        }
 
+Minimal configuration is needed for the scanner.  Unknown marks
+(punctuation) are not permitted so we ignore that.  A wide range of
+character are permitted in numbers, so that both period and comma can
+be used for the decimal marker, and both space and underscore can be
+used to separate groups of digits.  Only the defauls are allowed in
+identifiers with the exception that underscore can both start and
+continue an identifier.
+
+###### scanner configuration
+       .ignored = (1 << TK_mark),
+       .number_chars = ".,_+- ",
+       .word_start = "_",
+       .word_cont = "_",
+
 ### Analysis
 
-These four requirements of parse, analyse, print, interpret apply to
-each language element individually so that is how most of the code
-will be structured.
+The four requirements of parse, analyse, print, and interpret apply to
+each language element individually so that is how most of the code will
+be structured.
 
 Three of the four are fairly self explanatory.  The one that requires
 a little explanation is the analysis step.
 
-The current language design does not require (or even allow) the types
-of variables to be declared, but they must still have a single type.
-Different operations impose different requirements on the variables,
-for example addition requires both arguments to be numeric, and
-assignment requires the variable on the left to have the same type as
-the expression on the right.
+The current language design does not require the types of variables to
+be declared, but they must still have a single type.  Different
+operations impose different requirements on the variables, for example
+addition requires both arguments to be numeric, and assignment requires
+the variable on the left to have the same type as the expression on the
+right, or to be a reference to that type.  There are currently no type
+that are distinct but compatible, though that will change when more
+numeric types are introduced and again when interfaces are added.  Until
+then the type or a variable is determined either from the declaration of
+the initial assignment, but the code tries not to assume that.
 
 Analysis involves propagating these type requirements around and
-consequently setting the type of each variable.  If any requirements
-are violated (e.g. a string is compared with a number) or if a
-variable needs to have two different types, then an error is raised
-and the program will not run.
+consequently setting the type of each variable.  If any requirements are
+violated (e.g.  a string is compared with a number) or if a variable
+needs to have two different types, then an error is raised and the
+program will not run.
 
-If the same variable is declared in both branchs of an 'if/else', or
+If the same variable is declared in both branches of an 'if/else', or
 in all cases of a 'switch' then the multiple instances may be merged
-into just one variable if the variable is references after the
+into just one variable if the variable is referenced after the
 conditional statement.  When this happens, the types must naturally be
 consistent across all the branches.  When the variable is not used
 outside the if, the variables in the different branches are distinct
 and can be of different types.
 
-Determining the types of all variables early is important for
-processing command line arguments.  These can be assigned to any type
-of variable, but we must first know the correct type so any required
-conversion can happen.  If a variable is associated with a command
-line argument but no type can be interpreted (e.g. the variable is
-only ever used in a `print` statement), then the type is set to
-'string'.
-
-Undeclared names may only appear in "use" statements and "case" expressions.
-These names are given a type of "label" and a unique value.
-This allows them to fill the role of a name in an enumerated type, which
-is useful for testing the `switch` statement.
+Local variables, global constants, and functions are all named in the
+same namespace.  If a name is used before it is declared, it is assumed
+to be global, either a constant or a function.  It must be declare
+eventually, and this is checked in the analysis phase after all code has
+been parsed.
 
 As we will see, the condition part of a `while` statement can return
-either a Boolean or some other type.  This requires that the expect
-type that gets passed around comprises a type (`enum vtype`) and a
-flag to indicate that `Vbool` is also permitted.
-
-As there are, as yet, no distinct types that are compatible, there
-isn't much subtlety in the analysis.  When we have distinct number
-types, this will become more interesting.
+either a Boolean or some other type, and as we have seen an asignment to
+a reference allows either a reference or the refered-to type to be
+given.  This requires that the expected type that gets passed around is
+accompanied by some flags, one to indicate that `Boolean` is also
+permitted and one to indicate that a reference to the given type is also
+permitted.
+
+Possibly the most interesting part of analysis at present involves some
+flags can be set during analysis of an expression, such as whether it
+can be used and a "lvalue" (i.e.  it can be assigned to) and whether it
+can be computed at compile-time, or whether it must wait until runtime.
+These will be introduced in due course.
 
 #### Error reporting
 
 When analysis discovers an inconsistency it needs to report an error;
 just refusing to run the code ensures that the error doesn't cascade,
-but by itself it isn't very useful.  A clear understand of the sort of
-error message that are useful will help guide the process of analysis.
+but by itself it isn't very useful.  A clear understanding of the sort
+of error messages that are useful will help guide the process of
+analysis.
 
 At a simplistic level, the only sort of error that type analysis can
 report is that the type of some construct doesn't match a contextual
@@ -325,34 +385,47 @@ particular type, by indicating the location where the type was set,
 whether by declaration or usage.
 
 Using a recursive-descent analysis we can easily detect a problem at
-multiple locations. In "`hello:= "there"; 4 + hello`" the addition
-will detect that one argument is not a number and the usage of `hello`
-will detect that a number was wanted, but not provided.  In this
-(early) version of the language, we will generate error reports at
-multiple locations, so the use of `hello` will report an error and
-explain were the value was set, and the addition will report an error
-and say why numbers are needed.  To be able to report locations for
-errors, each language element will need to record a file location
-(line and column) and each variable will need to record the language
-element where its type was set.  For now we will assume that each line
-of an error message indicates one location in the file, and up to 2
-types.  So we provide a `printf`-like function which takes a format, a
-language (a `struct exec` which has not yet been introduced), and 2
-types. "`%1`" reports the first type, "`%2`" reports the second.  We
-will need a function to print the location, once we know how that is
-stored.  As will be explained later, there are sometimes extra rules for
-type matching and they might affect error messages, we need to pass those
-in too.
+multiple locations.  In "`hello := "there"; print 4 + hello`" the
+addition will detect that one argument is not a number and the usage of
+`hello` will detect that a number was wanted, but not provided.  We
+could generate an error at either location, or even at both.  In this
+version of the language, we pass down the expected type, and the handler
+for variables notices that `hello` is not the correct type and reports
+an error.  So errors are large reported at the leaves.
+
+To be able to report locations for errors, each language element will
+need to record a file location (line and column) and each variable will
+need to record the language element where its type was set.  For now we
+will assume that each line of an error message indicates one location in
+the file, and up to 2 types.  So we provide a `printf`-like function
+which takes a format, a location (a `struct exec` which has not yet been
+introduced), and 2 types.  "`%1`" reports the first type, "`%2`" reports
+the second.  We will need a function to print the location, once we know
+how that is stored.  As explained earlier, there are sometimes extra
+rules for type matching (to accept Bool or reference) and they might
+affect error messages, we need to pass those in too.
+
+As well as type errors, we sometimes need to report problems with
+tokens, which might be unexpected or badly formatted, or might be a name
+that has been defined twice or not at all.  For these we have
+`tok_err()` which reports an error with a given token.  Each of the
+error functions updates a count of error in the context to indicate that
+parsing failed.  We use a counter so it is easy to determine if a new
+error occurred during a particular stage of analysis.
 
 ###### forward decls
 
        static void fput_loc(struct exec *loc, FILE *f);
+       static void type_err(struct parse_context *c,
+                            char *fmt, struct exec *loc,
+                            struct type *t1, enum val_rules rules, struct type *t2);
+       static void tok_err(struct parse_context *c, char *fmt, struct token *t);
 
 ###### core functions
 
        static void type_err(struct parse_context *c,
                             char *fmt, struct exec *loc,
-                            struct type *t1, int rules, struct type *t2)
+                            struct type *t1, enum val_rules rules, struct type *t2)
        {
                fprintf(stderr, "%s:", c->file_name);
                fput_loc(loc, stderr);
@@ -363,419 +436,863 @@ in too.
                        }
                        fmt++;
                        switch (*fmt) {
-                       case '%': fputc(*fmt, stderr); break;
-                       default: fputc('?', stderr); break;
+                       case '%': fputc(*fmt, stderr); break;   // NOTEST
+                       default: fputc('?', stderr); break;     // NOTEST
                        case '1':
-                               if (t1)
-                                       fprintf(stderr, "%.*s", t1->name.len, t1->name.txt);
-                               else
-                                       fputs("*unknown*", stderr);
+                               type_print(t1, stderr);
                                break;
                        case '2':
-                               if (t2)
-                                       fprintf(stderr, "%.*s", t2->name.len, t2->name.txt);
-                               else
-                                       fputs("*unknown*", stderr);
-                               break;
+                               type_print(t2, stderr);
                                break;
                        ## format cases
                        }
                }
                fputs("\n", stderr);
-               c->parse_error = 1;
+               c->parse_error += 1;
        }
 
        static void tok_err(struct parse_context *c, char *fmt, struct token *t)
        {
-               fprintf(stderr, "%s:%d:%d: %s\n", c->file_name, t->line, t->col, fmt);
-               c->parse_error = 1;
+               fprintf(stderr, "%s:%d:%d: %s: %.*s\n", c->file_name, t->line, t->col, fmt,
+                       t->txt.len, t->txt.txt);
+               c->parse_error += 1;
        }
 
-## Data Structures
+## Entities: values, types, variables, and code.
 
-One last introductory step before detailing the language elements and
-providing their four requirements is to establish the data structures
-to store these elements.
+It could be said that the focus of a language is values, which are
+organised into types, stored in variables, and manipulated with
+executable code.  This section introduces each of these entities and
+provide a foundation for them.  Once they are all in place, the next
+section will flesh them out, particular the mode complex executable code
+entities.
 
-There are two key objects that we need to work with: executable
-elements which comprise the program, and values which the program
-works with.  Between these are the variables in their various scopes
-which hold the values, and types which classify the values stored and
-manipulatd by executables.
+### Executables
 
-### Types
+Executables can be lots of different things.  In many cases an
+executable is just an operation combined with one or two other
+executables.  This allows for expressions and lists etc.  Other times an
+executable is something quite specific like a constant or variable name.
+So we define a `struct exec` to be a general executable with a type, and
+a `struct binodes` which is a subclass of `exec`, forms a node in a
+binary tree, and holds an operation.  The simplest operation is "List"
+which can be used to combine several execs together.
+
+When parsing a list of binodes, whether with the `List` operator or some
+other, it is most convenient to append to the end, so a list is a list
+and a thin.  When using the list it is more convenient to consider
+a list to be a thing and a list.  So we need a function to re-order
+a list.  `reorder_bilist` serves this purpose.
+
+There will be other subclasses, and to access these we need to be able
+to `cast` the `exec` into the various other types.  The first field in
+any `struct exec` is the type from the `exec_types` enum.
 
-Values come in a wide range of types, with more likely to be added.
-Each type needs to be able to parse and print its own values (for
-convenience at least) as well as to compare two values, at least for
-equality and possibly for order.  For now, values might need to be
-duplicated and freed, though eventually such manipulations will be
-better integrated into the language.
+###### macros
+       #define cast(structname, pointer) ({            \
+               const typeof( ((struct structname *)0)->type) *__mptr =         \
+                                                        &(pointer)->type;      \
+               if (__mptr && *__mptr != X##structname) abort();                \
+               (struct structname *)( (char *)__mptr);})
 
-Rather than requiring every numeric type to support all numeric
-operations (add, multiple, etc), we allow types to be able to present
-as one of a few standard types: integer, float, and fraction.  The
-existance of these conversion functions enable types to determine if
-they are compatible with other types.
+       #define new(structname) ({                                              \
+               struct structname *__ptr = ((struct structname *)calloc(        \
+                                               1,sizeof(struct structname)));  \
+               __ptr->type = X##structname;                                    \
+               __ptr->line = -1; __ptr->column = -1;                           \
+               __ptr;})
 
-Named type are stored in a simple linked list.
+       #define new_pos(structname, token) ({                                   \
+               struct structname *__ptr = ((struct structname *)calloc(        \
+                                               1,sizeof(struct structname)));  \
+               __ptr->type = X##structname;                                    \
+               __ptr->line = token.line; __ptr->column = token.col;            \
+               __ptr;})
 
 ###### ast
-
-       struct type {
-               struct text name;
-               struct type *next;
-               struct value (*init)(struct type *type);
-               struct value (*parse)(struct type *type, char *str);
-               void (*print)(struct value val);
-               int (*cmp_order)(struct value v1, struct value v2);
-               int (*cmp_eq)(struct value v1, struct value v2);
-               struct value (*dup)(struct value val);
-               void (*free)(struct value val);
-               struct type *(*compat)(struct type *this, struct type *other);
-               long long (*to_int)(struct value *v);
-               double (*to_float)(struct value *v);
-               int (*to_mpq)(mpq_t *q, struct value *v);
-               union {
-                       ## type union fields
-               };
+       enum exec_types {
+               Xbinode,
+               ## exec type
        };
-
-       struct typep {
-               struct type *t;
+       struct exec {
+               enum exec_types type;
+               int line, column;
+               ## exec fields
+       };
+       struct binode {
+               struct exec;
+               enum Btype {
+                       List,
+                       ## Binode types
+               } op;
+               struct exec *left, *right;
        };
-
-###### parse context
-
-       struct type *typelist;
 
 ###### ast functions
 
-       static struct type *find_type(struct parse_context *c, struct text s)
+       static int __fput_loc(struct exec *loc, FILE *f)
        {
-               struct type *l = c->typelist;
-
-               while (l &&
-                      text_cmp(l->name, s) != 0)
-                               l = l->next;
-               return l;
+               if (!loc)
+                       return 0;
+               if (loc->line >= 0) {
+                       fprintf(f, "%d:%d: ", loc->line, loc->column);
+                       return 1;
+               }
+               if (loc->type == Xbinode)
+                       return __fput_loc(cast(binode,loc)->left, f) ||
+                              __fput_loc(cast(binode,loc)->right, f); // NOTEST
+               return 0;                                              // NOTEST
+       }
+       static void fput_loc(struct exec *loc, FILE *f)
+       {
+               if (!__fput_loc(loc, f))
+                       fprintf(f, "??:??: ");                  // NOTEST
        }
 
-       static struct type *add_type(struct parse_context *c, struct text s,
-                                    struct type *proto)
+       // Move all nodes from 'b' to 'rv', reversing their order.
+       // In 'b' 'left' is a list, and 'right' is the last node.
+       // In 'rv', left' is the first node and 'right' is a list.
+       static struct binode *reorder_bilist(struct binode *b)
        {
-               struct type *n;
+               struct binode *rv = NULL;
 
-               n = calloc(1, sizeof(*n));
-               *n = *proto;
-               n->name = s;
-               n->next = c->typelist;
-               c->typelist = n;
-               return n;
+               while (b) {
+                       struct exec *t = b->right;
+                       b->right = rv;
+                       rv = b;
+                       if (b->left)
+                               b = cast(binode, b->left);
+                       else
+                               b = NULL;
+                       rv->left = t;
+               }
+               return rv;
        }
 
-       static void free_type(struct type *t)
+Each different type of `exec` node needs a number of functions defined,
+a bit like methods.  We must be able to free it, print it, analyse it
+and execute it.  Once we have specific `exec` types we will need to
+parse them too.  Let's take this a bit more slowly.
+
+#### Freeing
+
+The parser generator requires a `free_foo` function for each struct
+that stores attributes and they will often be `exec`s and subtypes
+there-of.  So we need `free_exec` which can handle all the subtypes,
+and we need `free_binode`.
+
+###### ast functions
+
+       static void free_binode(struct binode *b)
        {
-               /* The type is always a reference to something in the
-                * context, so we don't need to free anything.
-                */
+               if (!b)
+                       return;
+               free_exec(b->left);
+               free_exec(b->right);
+               free(b);
        }
 
-       static void context_init(struct parse_context *c)
+###### core functions
+       static void free_exec(struct exec *e)
        {
-               ## context initialization
+               if (!e)
+                       return;
+               switch(e->type) {
+                       ## free exec cases
+               }
        }
 
-###### free context types
+###### forward decls
 
-       while (context.typelist) {
-               struct type *t = context.typelist;
+       static void free_exec(struct exec *e);
 
-               context.typelist = t->next;
-               free(t);
+###### free exec cases
+       case Xbinode: free_binode(cast(binode, e)); break;
+
+#### Printing
+
+Printing an `exec` requires that we know the current indent level for
+printing line-oriented components.  As will become clear later, we
+also want to know what sort of bracketing to use.  It will also be used
+to sometime print comments after an exec to explain some of the results
+of analysis.
+
+###### ast functions
+
+       static void do_indent(int i, char *str)
+       {
+               while (i-- > 0)
+                       printf("    ");
+               printf("%s", str);
+       }
+
+###### core functions
+       static void print_binode(struct binode *b, int indent, int bracket)
+       {
+               struct binode *b2;
+               switch(b->op) {
+               case List: abort(); // must be handled by parent NOTEST
+               ## print binode cases
+               }
        }
 
-### Values
+       static void print_exec(struct exec *e, int indent, int bracket)
+       {
+               if (!e)
+                       return; // NOTEST
+               switch (e->type) {
+               case Xbinode:
+                       print_binode(cast(binode, e), indent, bracket); break;
+               ## print exec cases
+               }
+               ## print exec extras
+       }
 
-Values can be numbers, which we represent as multi-precision
-fractions, strings, Booleans and labels.  When analysing the program
-we also need to allow for places where no value is meaningful (type
-`Tnone`) and where we don't know what type to expect yet (type is
-`NULL`).
+###### forward decls
 
-Values are never shared, they are always copied when used, and freed
-when no longer needed.
+       static void print_exec(struct exec *e, int indent, int bracket);
 
-When propagating type information around the program, we need to
-determine if two types are compatible, where type `NULL` is compatible
-with anything.  There are two special cases with type compatibility,
-both related to the Conditional Statement which will be described
-later.  In some cases a Boolean can be accepted as well as some other
-primary type, and in others any type is acceptable except a label (`Vlabel`).
-A separate function encode these cases will simplify some code later.
+#### Analysing
 
-When assigning command line arguments to variables, we need to be able
-to parse each type from a string.
+As discussed, analysis involves propagating type requirements around the
+program and looking for errors.
 
-###### includes
-       #include <gmp.h>
-       #include "string.h"
-       #include "number.h"
+So `propagate_types` is passed an expected type (being a `struct type`
+pointer together with some `val_rules` flags) that the `exec` is
+expected to return, and returns the type that it does return, either of
+which can be `NULL` signifying "unknown".  A `prop_err` flag set is
+passed by reference.  It has `Efail` set when an error is found, and
+`Eretry` when the type for some element is set via propagation.  If
+any expression cannot be evaluated a compile time, `Eruntime` is set.
+If the expression can be copied, `Emaycopy` is set.
 
-###### libs
-       myLDLIBS := libnumber.o libstring.o -lgmp
-       LDLIBS := $(filter-out $(myLDLIBS),$(LDLIBS)) $(myLDLIBS)
+If `Erval` is set, then the value cannot be assigned to because it is
+a temporary result.  If `Erval` is clear but `Econst` is set, then
+the value can only be assigned once, when the variable is declared.
 
-###### type union fields
-       enum vtype {Vnone, Vstr, Vnum, Vbool, Vlabel} vtype;
+Various propagate cases can pass "perr_local" to analyse components of
+an expression which do not affect the result type of the whole
+expression.
 
 ###### ast
-       struct value {
-               struct type *type;
-               union {
-                       struct text str;
-                       mpq_t num;
-                       int bool;
-                       void *label;
-               };
-       };
 
-       enum val_rules {Rnolabel = 1<<0, Rboolok = 1<<1};
+       enum val_rules {Rboolok = 1<<0, Rrefok = 1<<1,};
+       enum prop_err {Efail = 1<<0, Eretry = 1<<1, Eruntime = 1<<2,
+                      Emaycopy = 1<<3, Erval = 1<<4, Econst = 1<<5};
 
-###### format cases
-       case 'r':
-               if (rules & Rnolabel)
-                       fputs(" (labels not permitted)", stderr);
-               break;
+###### forward decls
+       static struct type *propagate_types(
+               struct exec *prog, struct parse_context *c, enum prop_err *perr,
+               struct type *type, enum val_rules rules);
+###### core functions
 
-###### ast functions
-       static void _free_value(struct value v)
+       static struct type *__propagate_types(
+               struct exec *prog, struct parse_context *c,
+               enum prop_err *perr, enum prop_err *perr_local,
+               struct type *type, enum val_rules rules)
        {
-               switch (v.type->vtype) {
-               case Vnone: break;
-               case Vstr: free(v.str.txt); break;
-               case Vnum: mpq_clear(v.num); break;
-               case Vlabel:
-               case Vbool: break;
+               struct type *t;
+
+               if (!prog)
+                       return Tnone;
+
+               switch (prog->type) {
+               case Xbinode:
+               {
+                       struct binode *b = cast(binode, prog);
+                       switch (b->op) {
+                       case List: abort(); // NOTEST
+                       ## propagate binode cases
+                       }
+                       break;
                }
+               ## propagate exec cases
+               }
+               return Tnone;
        }
 
-       static void free_value(struct value v)
+       static struct type *propagate_types(struct exec *prog,
+                                           struct parse_context *c,
+                                           enum prop_err *perr,
+                                           struct type *type,
+                                           enum val_rules rules)
        {
-               if (v.type)
-                       v.type->free(v);
+               int pre_err = c->parse_error;
+               enum prop_err perr_local = 0;
+               struct type *ret = __propagate_types(prog, c, perr, &perr_local,
+                                                    type, rules);
+
+               *perr |= perr_local & (Efail | Eretry);
+               if (c->parse_error > pre_err)
+                       *perr |= Efail;
+               return ret;
        }
 
-       static int vtype_compat(struct type *require, struct type *have, int rules)
-       {
-               if ((rules & Rboolok) && have == Tbool)
-                       return 1;
-               if ((rules & Rnolabel) && have == Tlabel)
-                       return 0;
-               if (!require || !have)
-                       return 1;
+#### Interpreting
 
-               return require == have;
-       }
+Interpreting an `exec` primarily requires the `exec` and the variable
+storage information stored in the parse state.  Apart from modifying
+those variables, and possibly performing other side-effects, an exec can
+return a value.  `struct value` is used for passing around small values
+and a pointer to that structure can be used for larger values.
 
-###### value functions
+Specifically, each `exec` case can return a value combined with a type
+in `struct lrval`.  The type may be `Tnone` but must be non-NULL.  Some
+`exec`s will return the location of a value, which can be updated, in
+`lval`.  Others will set `lval` to NULL indicating that there is a value
+of appropriate type in `rval`.
 
-       static struct value _val_init(struct type *type)
-       {
-               struct value rv;
+Callers call either `interp_exec()` if they just want the value, or
+`linterp_exec()~ if they need an lvalue.  `dinterp_exec()` is called
+when there is a destination for the value to go.  This is used for
+function calls which return a value that is not an lvalue, but is too
+large to store in `struct value`.
 
-               rv.type = type;
-               switch(type->vtype) {
-               case Vnone:
-                       break;
-               case Vnum:
-                       mpq_init(rv.num); break;
-               case Vstr:
-                       rv.str.txt = malloc(1);
-                       rv.str.len = 0;
-                       break;
-               case Vbool:
-                       rv.bool = 0;
-                       break;
-               case Vlabel:
-                       rv.label = NULL;
-                       break;
-               }
-               return rv;
+Each of these call `_interp_exec()` which calls the appropriate exec case.
+
+###### forward decls
+       static struct value interp_exec(struct parse_context *c, struct exec *e,
+                                       struct type **typeret);
+###### ast
+
+       struct value {
+               union {
+                       char ptr[1];
+                       ## value union fields
+               };
+       };
+
+       struct lrval {
+               struct type *type;
+               struct value rval, *lval;
+       };
+
+###### core functions
+
+       /* If dest is passed, dtype must give the expected type, and
+        * result can go there, in which case type is returned as NULL.
+        */
+       static struct lrval _interp_exec(struct parse_context *c, struct exec *e,
+                                        struct value *dest, struct type *dtype);
+
+       static struct value interp_exec(struct parse_context *c, struct exec *e,
+                                       struct type **typeret)
+       {
+               struct lrval ret = _interp_exec(c, e, NULL, NULL);
+
+               if (!ret.type) abort();
+               if (typeret)
+                       *typeret = ret.type;
+               if (ret.lval)
+                       dup_value(ret.type, ret.lval, &ret.rval);
+               return ret.rval;
        }
 
-       static struct value val_init(struct type *type)
+       static struct value *linterp_exec(struct parse_context *c, struct exec *e,
+                                         struct type **typeret)
        {
-               struct value rv;
+               struct lrval ret = _interp_exec(c, e, NULL, NULL);
 
-               if (type)
-                       return type->init(type);
-               rv.type = type;
-               return rv;
+               if (!ret.type) abort();
+               if (ret.lval)
+                       *typeret = ret.type;
+               else
+                       free_value(ret.type, &ret.rval);
+               return ret.lval;
        }
 
-       static struct value _dup_value(struct value v)
+       /* dinterp_exec is used when the destination type is certain and
+        * the value has a place to go.
+        */
+       static void dinterp_exec(struct parse_context *c, struct exec *e,
+                                struct value *dest, struct type *dtype,
+                                int need_free)
        {
-               struct value rv;
-               rv.type = v.type;
-               switch (rv.type->vtype) {
-               case Vnone:
-                       break;
-               case Vlabel:
-                       rv.label = v.label;
-                       break;
-               case Vbool:
-                       rv.bool = v.bool;
-                       break;
-               case Vnum:
-                       mpq_init(rv.num);
-                       mpq_set(rv.num, v.num);
-                       break;
-               case Vstr:
-                       rv.str.len = v.str.len;
-                       rv.str.txt = malloc(rv.str.len);
-                       memcpy(rv.str.txt, v.str.txt, v.str.len);
+               struct lrval ret = _interp_exec(c, e, dest, dtype);
+               if (!ret.type)
+                       return;
+               if (need_free)
+                       free_value(dtype, dest);
+               if (ret.lval)
+                       dup_value(dtype, ret.lval, dest);
+               else
+                       memcpy(dest, &ret.rval, dtype->size);
+       }
+
+       static struct lrval _interp_exec(struct parse_context *c, struct exec *e,
+                                        struct value *dest, struct type *dtype)
+       {
+               /* If the result is copied to dest, ret.type is set to NULL */
+               struct lrval ret;
+               struct value rv = {}, *lrv = NULL;
+               struct type *rvtype;
+
+               rvtype = ret.type = Tnone;
+               if (!e) {
+                       ret.lval = lrv;
+                       ret.rval = rv;
+                       return ret;
+               }
+
+               switch(e->type) {
+               case Xbinode:
+               {
+                       struct binode *b = cast(binode, e);
+                       struct value left, right, *lleft;
+                       struct type *ltype, *rtype;
+                       ltype = rtype = Tnone;
+                       switch (b->op) {
+                       case List: abort();     // NOTEST
+                       ## interp binode cases
+                       }
+                       free_value(ltype, &left);
+                       free_value(rtype, &right);
                        break;
                }
-               return rv;
+               ## interp exec cases
+               }
+               if (rvtype) {
+                       ret.lval = lrv;
+                       ret.rval = rv;
+                       ret.type = rvtype;
+               }
+               ## interp exec cleanup
+               return ret;
+       }
+
+### Types
+
+Values come in a wide range of types, with more likely to be added.
+Each type needs to be able to print its own values (for convenience at
+least, and for printing manifest constants when generating code) as well
+as to compare two values, at least for equality and possibly for order.
+For now, values might need to be duplicated and freed, though eventually
+such manipulations will be better integrated into the language.
+
+Named type are stored in a simple linked list.  Objects of each type are
+"values" which are often passed around by value.
+
+There are both explicitly named types, and anonymous types.  Anonymous
+cannot be accessed by name, but are used internally and have a name
+which might be reported in error messages.
+
+The `prepare_type()` interface is called on a type in two circumstances.
+After the program has been parsed but before anything in executed it is
+called with `parse_time` set to one.  This can be used for processing
+information that was not fully available when the type description was
+parsed, such as types of fields in structures.  It is then called again
+at runtime when a variable declaration is processed.  This allows the
+details of a type to depend on runtime context, such as the size of an
+array being determined by a constant.  In this second case the
+`parse_time` parameter is set to zero.
+
+###### ast late
+       struct type {
+               struct text name;
+               struct type *next;
+               struct token first_use;
+               int size, align;
+               int anon;
+               void (*init)(struct type *type, struct value *val);
+               int (*prepare_type)(struct parse_context *c, struct type *type,
+                    int parse_time);
+               void (*print)(struct type *type, struct value *val, FILE *f);
+               void (*print_type)(struct type *type, FILE *f);
+               int (*cmp_order)(struct type *t1, struct type *t2,
+                                struct value *v1, struct value *v2);
+               int (*cmp_eq)(struct type *t1, struct type *t2,
+                             struct value *v1, struct value *v2);
+               void (*dup)(struct type *type, struct value *vold,
+                           struct value *vnew);
+               int (*test)(struct type *type, struct value *val);
+               void (*free)(struct type *type, struct value *val);
+               void (*free_type)(struct type *t);
+               ## type functions
+               union {
+                       ## type union fields
+               };
+       };
+
+###### parse context
+
+       struct type *typelist;
+
+###### includes
+       #include <stdarg.h>
+
+###### ast functions
+
+       static struct type *find_type(struct parse_context *c, struct text s)
+       {
+               struct type *t = c->typelist;
+
+               while (t && (t->anon ||
+                            text_cmp(t->name, s) != 0))
+                               t = t->next;
+               return t;
        }
 
-       static struct value dup_value(struct value v)
+       static struct type *_add_type(struct parse_context *c, struct text s,
+                                    struct type *proto, int anon)
        {
-               if (v.type)
-                       return v.type->dup(v);
-               return v;
+               struct type *n;
+
+               n = calloc(1, sizeof(*n));
+               if (proto)
+                       *n = *proto;
+               else
+                       n->size = -1;
+               n->name = s;
+               n->anon = anon;
+               n->next = c->typelist;
+               c->typelist = n;
+               return n;
+       }
+
+       static struct type *add_type(struct parse_context *c, struct text s,
+                                     struct type *proto)
+       {
+               return _add_type(c, s, proto, 0);
        }
 
-       static int _value_cmp(struct value left, struct value right)
+       static struct type *add_anon_type(struct parse_context *c,
+                                         struct type *proto, char *name, ...)
        {
-               int cmp;
-               if (left.type != right.type)
-                       return left.type - right.type;
-               switch (left.type->vtype) {
-               case Vlabel: cmp = left.label == right.label ? 0 : 1; break;
-               case Vnum: cmp = mpq_cmp(left.num, right.num); break;
-               case Vstr: cmp = text_cmp(left.str, right.str); break;
-               case Vbool: cmp = left.bool - right.bool; break;
-               case Vnone: cmp = 0;
+               struct text t;
+               va_list ap;
+
+               va_start(ap, name);
+               vasprintf(&t.txt, name, ap);
+               va_end(ap);
+               t.len = strlen(t.txt);
+               return _add_type(c, t, proto, 1);
+       }
+
+       static struct type *find_anon_type(struct parse_context *c,
+                                          struct type *proto, char *name, ...)
+       {
+               struct type *t = c->typelist;
+               struct text nm;
+               va_list ap;
+
+               va_start(ap, name);
+               vasprintf(&nm.txt, name, ap);
+               va_end(ap);
+               nm.len = strlen(name);
+
+               while (t && (!t->anon ||
+                            text_cmp(t->name, nm) != 0))
+                               t = t->next;
+               if (t) {
+                       free(nm.txt);
+                       return t;
                }
-               return cmp;
+               return _add_type(c, nm, proto, 1);
        }
 
-       static int value_cmp(struct value left, struct value right)
+       static void free_type(struct type *t)
        {
-               if (left.type && left.type->cmp_order)
-                       return left.type->cmp_order(left, right);
-               if (left.type && left.type->cmp_eq)
-                       return left.type->cmp_eq(left, right);
-               return -1;
+               /* The type is always a reference to something in the
+                * context, so we don't need to free anything.
+                */
        }
 
-       static struct text text_join(struct text a, struct text b)
+       static void free_value(struct type *type, struct value *v)
        {
-               struct text rv;
-               rv.len = a.len + b.len;
-               rv.txt = malloc(rv.len);
-               memcpy(rv.txt, a.txt, a.len);
-               memcpy(rv.txt+a.len, b.txt, b.len);
-               return rv;
+               if (type && v) {
+                       type->free(type, v);
+                       memset(v, 0x5a, type->size);
+               }
        }
 
-       static void _print_value(struct value v)
+       static void type_print(struct type *type, FILE *f)
        {
-               switch (v.type->vtype) {
-               case Vnone:
-                       printf("*no-value*"); break;
-               case Vlabel:
-                       printf("*label-%p*", v.label); break;
-               case Vstr:
-                       printf("%.*s", v.str.len, v.str.txt); break;
-               case Vbool:
-                       printf("%s", v.bool ? "True":"False"); break;
-               case Vnum:
-                       {
-                       mpf_t fl;
-                       mpf_init2(fl, 20);
-                       mpf_set_q(fl, v.num);
-                       gmp_printf("%Fg", fl);
-                       mpf_clear(fl);
-                       break;
+               if (!type)
+                       fputs("*unknown*type*", f);     // NOTEST
+               else if (type->name.len && !type->anon)
+                       fprintf(f, "%.*s", type->name.len, type->name.txt);
+               else if (type->print_type)
+                       type->print_type(type, f);
+               else if (type->name.len && type->anon)
+                       fprintf(f, "\"%.*s\"", type->name.len, type->name.txt);
+               else
+                       fputs("*invalid*type*", f);     // NOTEST
+       }
+
+       static void val_init(struct type *type, struct value *val)
+       {
+               if (type && type->init)
+                       type->init(type, val);
+       }
+
+       static void dup_value(struct type *type,
+                             struct value *vold, struct value *vnew)
+       {
+               if (type && type->dup)
+                       type->dup(type, vold, vnew);
+       }
+
+       static int value_cmp(struct type *tl, struct type *tr,
+                            struct value *left, struct value *right)
+       {
+               if (tl && tl->cmp_order)
+                       return tl->cmp_order(tl, tr, left, right);
+               if (tl && tl->cmp_eq)
+                       return tl->cmp_eq(tl, tr, left, right);
+               return -1;                              // NOTEST
+       }
+
+       static void print_value(struct type *type, struct value *v, FILE *f)
+       {
+               if (type && type->print)
+                       type->print(type, v, f);
+               else
+                       fprintf(f, "*Unknown*");                // NOTEST
+       }
+
+       static void prepare_types(struct parse_context *c)
+       {
+               struct type *t;
+               int retry = 1;
+               enum { none, some, cannot } progress = none;
+
+               while (retry) {
+                       retry = 0;
+
+                       for (t = c->typelist; t; t = t->next) {
+                               if (t->size < 0)
+                                       tok_err(c, "error: type used but not declared",
+                                                &t->first_use);
+                               if (t->size == 0 && t->prepare_type) {
+                                       if (t->prepare_type(c, t, 1))
+                                               progress = some;
+                                       else if (progress == cannot)
+                                               tok_err(c, "error: type has recursive definition",
+                                                       &t->first_use);
+                                       else
+                                               retry = 1;
+                               }
+                       }
+                       switch (progress) {
+                       case cannot:
+                               retry = 0; break;
+                       case none:
+                               progress = cannot; break;
+                       case some:
+                               progress = none; break;
                        }
                }
        }
 
-       static void print_value(struct value v)
+###### forward decls
+
+       static void free_value(struct type *type, struct value *v);
+       static int type_compat(struct type *require, struct type *have, enum val_rules rules);
+       static void type_print(struct type *type, FILE *f);
+       static void val_init(struct type *type, struct value *v);
+       static void dup_value(struct type *type,
+                             struct value *vold, struct value *vnew);
+       static int value_cmp(struct type *tl, struct type *tr,
+                            struct value *left, struct value *right);
+       static void print_value(struct type *type, struct value *v, FILE *f);
+
+###### free context types
+
+       while (context.typelist) {
+               struct type *t = context.typelist;
+
+               context.typelist = t->next;
+               if (t->free_type)
+                       t->free_type(t);
+               if (t->anon)
+                       free(t->name.txt);
+               free(t);
+       }
+
+Type can be specified for local variables, for fields in a structure,
+for formal parameters to functions, and possibly elsewhere.  Different
+rules may apply in different contexts.  As a minimum, a named type may
+always be used.  Currently the type of a formal parameter can be
+different from types in other contexts, so we have a separate grammar
+symbol for those.
+
+###### Grammar
+
+       $*type
+       Type -> IDENTIFIER ${
+               $0 = find_type(c, $ID.txt);
+               if (!$0) {
+                       $0 = add_type(c, $ID.txt, NULL);
+                       $0->first_use = $ID;
+               }
+       }$
+       ## type grammar
+
+       FormalType -> Type ${ $0 = $<1; }$
+       ## formal type grammar
+
+#### Base Types
+
+Values of the base types can be numbers, which we represent as
+multi-precision fractions, strings, Booleans and labels.  When
+analysing the program we also need to allow for places where no value
+is meaningful (type `Tnone`) and where we don't know what type to
+expect yet (type is `NULL`).
+
+Values are never shared, they are always copied when used, and freed
+when no longer needed.
+
+When propagating type information around the program, we need to
+determine if two types are compatible, where type `NULL` is compatible
+with anything.  There are two special cases with type compatibility.
+In some cases a Boolean can be accepted as well as some other
+primary type.  In other cases a reference to the given type is
+acceptable in place of a value of the type itself.
+
+###### type functions
+
+       int (*compat)(struct type *this, struct type *other, enum val_rules rules);
+
+###### ast functions
+
+       static int type_compat(struct type *require, struct type *have,
+                              enum val_rules rules)
        {
-               if (v.type && v.type->print)
-                       v.type->print(v);
-               else
-                       printf("*Unknown*");
+               if (!require || !have)
+                       return 1;
+
+               if (require->compat)
+                       return require->compat(require, have, rules);
+
+               return require == have;
        }
 
-       static struct value _parse_value(struct type *type, char *arg)
+###### includes
+       #include <gmp.h>
+       #include "parse_string.h"
+       #include "parse_number.h"
+
+###### libs
+       myLDLIBS := libnumber.o libstring.o -lgmp
+       LDLIBS := $(filter-out $(myLDLIBS),$(LDLIBS)) $(myLDLIBS)
+
+###### type union fields
+       enum vtype {Vnone, Vstr, Vnum, Vbool, Vlabel} vtype;
+
+###### value union fields
+       struct text str;
+       mpq_t num;
+       unsigned char bool;
+       int label;
+
+###### ast functions
+       static void _free_value(struct type *type, struct value *v)
        {
-               struct value val;
-               struct text tx;
-               int neg = 0;
-               char tail[3] = "";
+               if (!v)
+                       return;         // NOTEST
+               switch (type->vtype) {
+               case Vnone: break;
+               case Vstr: free(v->str.txt); break;
+               case Vnum: mpq_clear(v->num); break;
+               case Vlabel:
+               case Vbool: break;
+               }
+       }
+
+###### value functions
 
-               val.type = type;
+       static void _val_init(struct type *type, struct value *val)
+       {
                switch(type->vtype) {
-               case Vlabel:
-               case Vnone:
-                       val.type = NULL;
-                       break;
+               case Vnone:             // NOTEST
+                       break;          // NOTEST
+               case Vnum:
+                       mpq_init(val->num); break;
                case Vstr:
-                       val.str.len = strlen(arg);
-                       val.str.txt = malloc(val.str.len);
-                       memcpy(val.str.txt, arg, val.str.len);
+                       val->str.txt = malloc(1);
+                       val->str.len = 0;
                        break;
-               case Vnum:
-                       if (*arg == '-') {
-                               neg = 1;
-                               arg++;
-                       }
-                       tx.txt = arg; tx.len = strlen(tx.txt);
-                       if (number_parse(val.num, tail, tx) == 0)
-                               mpq_init(val.num);
-                       else if (neg)
-                               mpq_neg(val.num, val.num);
-                       if (tail[0]) {
-                               printf("Unsupported suffix: %s\n", arg);
-                               val.type = NULL;
-                       }
+               case Vbool:
+                       val->bool = 0;
                        break;
+               case Vlabel:
+                       val->label = 0; // NOTEST
+                       break;          // NOTEST
+               }
+       }
+
+       static void _dup_value(struct type *type,
+                              struct value *vold, struct value *vnew)
+       {
+               switch (type->vtype) {
+               case Vnone:             // NOTEST
+                       break;          // NOTEST
+               case Vlabel:
+                       vnew->label = vold->label;      // NOTEST
+                       break;          // NOTEST
                case Vbool:
-                       if (strcasecmp(arg, "true") == 0 ||
-                           strcmp(arg, "1") == 0)
-                               val.bool = 1;
-                       else if (strcasecmp(arg, "false") == 0 ||
-                                strcmp(arg, "0") == 0)
-                               val.bool = 0;
-                       else {
-                               printf("Bad bool: %s\n", arg);
-                               val.type = NULL;
-                       }
+                       vnew->bool = vold->bool;
+                       break;
+               case Vnum:
+                       mpq_init(vnew->num);
+                       mpq_set(vnew->num, vold->num);
+                       break;
+               case Vstr:
+                       vnew->str.len = vold->str.len;
+                       vnew->str.txt = malloc(vnew->str.len);
+                       memcpy(vnew->str.txt, vold->str.txt, vnew->str.len);
                        break;
                }
-               return val;
        }
 
-       static struct value parse_value(struct type *type, char *arg)
+       static int _value_cmp(struct type *tl, struct type *tr,
+                             struct value *left, struct value *right)
        {
-               struct value rv;
+               int cmp;
+               if (tl != tr)
+                       return tl - tr;
+               switch (tl->vtype) {
+               case Vlabel: cmp = left->label == right->label ? 0 : 1; break;
+               case Vnum: cmp = mpq_cmp(left->num, right->num); break;
+               case Vstr: cmp = text_cmp(left->str, right->str); break;
+               case Vbool: cmp = left->bool - right->bool; break;
+               case Vnone: cmp = 0;                    // NOTEST
+               }
+               return cmp;
+       }
 
-               if (type && type->parse)
-                       return type->parse(type, arg);
-               rv.type = NULL;
-               return rv;
+       static void _print_value(struct type *type, struct value *v, FILE *f)
+       {
+               switch (type->vtype) {
+               case Vnone:                             // NOTEST
+                       fprintf(f, "*no-value*"); break;        // NOTEST
+               case Vlabel:                            // NOTEST
+                       fprintf(f, "*label-%d*", v->label); break; // NOTEST
+               case Vstr:
+                       fprintf(f, "%.*s", v->str.len, v->str.txt); break;
+               case Vbool:
+                       fprintf(f, "%s", v->bool ? "True":"False"); break;
+               case Vnum:
+                       {
+                       mpf_t fl;
+                       mpf_init2(fl, 20);
+                       mpf_set_q(fl, v->num);
+                       gmp_fprintf(f, "%.10Fg", fl);
+                       mpf_clear(fl);
+                       break;
+                       }
+               }
        }
 
-       static void _free_value(struct value v);
+       static void _free_value(struct type *type, struct value *v);
+
+       static int bool_test(struct type *type, struct value *v)
+       {
+               return v->bool;
+       }
 
        static struct type base_prototype = {
                .init = _val_init,
-               .parse = _parse_value,
                .print = _print_value,
                .cmp_order = _value_cmp,
                .cmp_eq = _value_cmp,
@@ -786,59 +1303,260 @@ to parse each type from a string.
        static struct type *Tbool, *Tstr, *Tnum, *Tnone, *Tlabel;
 
 ###### ast functions
-       static struct type *add_base_type(struct parse_context *c, char *n, enum vtype vt)
+       static struct type *add_base_type(struct parse_context *c, char *n,
+                                         enum vtype vt, int size)
        {
                struct text txt = { n, strlen(n) };
                struct type *t;
 
                t = add_type(c, txt, &base_prototype);
                t->vtype = vt;
+               t->size = size;
+               t->align = size > sizeof(void*) ? sizeof(void*) : size;
+               if (t->size & (t->align - 1))
+                       t->size = (t->size | (t->align - 1)) + 1;       // NOTEST
                return t;
        }
 
-###### forward decls
-       static struct type *add_base_type(struct parse_context *c, char *n, enum vtype vt);
-
 ###### context initialization
 
-       Tbool  = add_base_type(c, "Boolean", Vbool);
-       Tstr   = add_base_type(c, "string", Vstr);
-       Tnum   = add_base_type(c, "number", Vnum);
-       Tnone  = add_base_type(c, "none", Vnone);
-       Tlabel = add_base_type(c, "label", Vlabel);
+       Tbool  = add_base_type(&context, "Boolean", Vbool, sizeof(char));
+       Tbool->test = bool_test;
+       Tstr   = add_base_type(&context, "string", Vstr, sizeof(struct text));
+       Tnum   = add_base_type(&context, "number", Vnum, sizeof(mpq_t));
+       Tnone  = add_base_type(&context, "none", Vnone, 0);
+       Tlabel = add_base_type(&context, "label", Vlabel, sizeof(void*));
 
-### Variables
+##### Base Values
 
-Variables are scoped named values.  We store the names in a linked
-list of "bindings" sorted lexically, and use sequential search and
-insertion sort.
+We have already met values as separate objects.  When manifest constants
+appear in the program text, that must result in an executable which has
+a constant value.  So the `val` structure embeds a value in an
+executable.
 
-###### ast
+###### exec type
+       Xval,
 
-       struct binding {
-               struct text name;
-               struct binding *next;   // in lexical order
-               ## binding fields
+###### ast
+       struct val {
+               struct exec;
+               struct type *vtype;
+               struct value val;
        };
 
-This linked list is stored in the parse context so that "reduce"
-functions can find or add variables, and so the analysis phase can
-ensure that every variable gets a type.
+###### ast functions
+       struct val *new_val(struct type *T, struct token tk)
+       {
+               struct val *v = new_pos(val, tk);
+               v->vtype = T;
+               return v;
+       }
 
-###### parse context
+###### declare terminals
+       $TERM True False
 
-       struct binding *varlist;  // In lexical order
+###### Grammar
 
-###### ast functions
+       $*val
+       Value ->  True ${
+               $0 = new_val(Tbool, $1);
+               $0->val.bool = 1;
+       }$
+       | False ${
+               $0 = new_val(Tbool, $1);
+               $0->val.bool = 0;
+       }$
+       | NUMBER ${ {
+               char tail[3] = "";
+               $0 = new_val(Tnum, $1);
+               if (number_parse($0->val.num, tail, $1.txt) == 0) {
+                       mpq_init($0->val.num);
+                       tok_err(c, "error: unsupported number format", &$NUM);
+               } else if (tail[0])
+                       tok_err(c, "error: unsupported number suffix", &$1);
+       } }$
+       | STRING ${ {
+               char tail[3];
+               $0 = new_val(Tstr, $1);
+               string_parse(&$1, '\\', &$0->val.str, tail);
+               if (tail[0])
+                       tok_err(c, "error: unsupported string suffix",
+                               &$1);
+       } }$
+       | MULTI_STRING ${ {
+               char tail[3];
+               $0 = new_val(Tstr, $1);
+               string_parse(&$1, '\\', &$0->val.str, tail);
+               if (tail[0])
+                       tok_err(c, "error: unsupported string suffix",
+                               &$1);
+       } }$
 
-       static struct binding *find_binding(struct parse_context *c, struct text s)
+###### print exec cases
+       case Xval:
        {
-               struct binding **l = &c->varlist;
-               struct binding *n;
-               int cmp = 1;
-
-               while (*l &&
-                       (cmp = text_cmp((*l)->name, s)) < 0)
+               struct val *v = cast(val, e);
+               if (v->vtype == Tstr)
+                       printf("\"");
+               // FIXME how to ensure numbers have same precision.
+               print_value(v->vtype, &v->val, stdout);
+               if (v->vtype == Tstr)
+                       printf("\"");
+               break;
+       }
+
+###### propagate exec cases
+       case Xval:
+       {
+               struct val *val = cast(val, prog);
+               if (!type_compat(type, val->vtype, rules))
+                       type_err(c, "error: expected %1 found %2",
+                                  prog, type, rules, val->vtype);
+               *perr |= Erval;
+               return val->vtype;
+       }
+
+###### interp exec cases
+       case Xval:
+               rvtype = cast(val, e)->vtype;
+               dup_value(rvtype, &cast(val, e)->val, &rv);
+               break;
+
+###### ast functions
+       static void free_val(struct val *v)
+       {
+               if (v)
+                       free_value(v->vtype, &v->val);
+               free(v);
+       }
+
+###### free exec cases
+       case Xval: free_val(cast(val, e)); break;
+
+#### Labels
+
+Labels are a temporary concept until I implement enums.  There are an
+anonymous enum which is declared by usage.  They are only allowed in
+`use` statements and corresponding `case` entries.  They appear as a
+period followed by an identifier.  All identifiers that are "used" must
+have a "case".
+
+For now, we have a global list of labels, and don't check that all "use"
+match "case".
+
+###### exec type
+       Xlabel,
+
+###### ast
+       struct label {
+               struct exec;
+               struct text name;
+               int value;
+       };
+###### free exec cases
+       case Xlabel:
+               free(e);
+               break;
+###### print exec cases
+       case Xlabel: {
+               struct label *l = cast(label, e);
+               printf(".%.*s", l->name.len, l->name.txt);
+               break;
+       }
+
+###### ast
+       struct labels {
+               struct labels *next;
+               struct text name;
+               int value;
+       };
+###### parse context
+       struct labels *labels;
+       int next_label;
+###### ast functions
+       static int label_lookup(struct parse_context *c, struct text name)
+       {
+               struct labels *l, **lp = &c->labels;
+               while (*lp && text_cmp((*lp)->name, name) < 0)
+                       lp = &(*lp)->next;
+               if (*lp && text_cmp((*lp)->name, name) == 0)
+                       return (*lp)->value;
+               l = calloc(1, sizeof(*l));
+               l->next = *lp;
+               l->name = name;
+               if (c->next_label == 0)
+                       c->next_label = 2;
+               l->value = c->next_label;
+               c->next_label += 1;
+               *lp = l;
+               return l->value;
+       }
+
+###### free context storage
+       while (context.labels) {
+               struct labels *l = context.labels;
+               context.labels = l->next;
+               free(l);
+       }
+
+###### declare terminals
+       $TERM .
+###### term grammar
+       | . IDENTIFIER ${ {
+               struct label *l = new_pos(label, $ID);
+               l->name = $ID.txt;
+               $0 = l;
+       } }$
+###### propagate exec cases
+       case Xlabel: {
+               struct label *l = cast(label, prog);
+               l->value = label_lookup(c, l->name);
+               if (!type_compat(type, Tlabel, rules))
+                       type_err(c, "error: expected %1 found %2",
+                                prog, type, rules, Tlabel);
+               *perr |= Erval;
+               return Tlabel;
+       }
+###### interp exec cases
+       case Xlabel : {
+               struct label *l = cast(label, e);
+               rv.label = l->value;
+               rvtype = Tlabel;
+               break;
+       }
+
+### Variables
+
+Variables are scoped named values.  We store the names in a linked list
+of "bindings" sorted in lexical order, and use sequential search and
+insertion sort.
+
+###### ast
+
+       struct binding {
+               struct text name;
+               struct binding *next;   // in lexical order
+               ## binding fields
+       };
+
+This linked list is stored in the parse context so that "reduce"
+functions can find or add variables, and so the analysis phase can
+ensure that every variable gets a type.
+
+###### parse context
+
+       struct binding *varlist;  // In lexical order
+
+###### ast functions
+
+       static struct binding *find_binding(struct parse_context *c, struct text s)
+       {
+               struct binding **l = &c->varlist;
+               struct binding *n;
+               int cmp = 1;
+
+               while (*l &&
+                       (cmp = text_cmp((*l)->name, s)) < 0)
                                l = & (*l)->next;
                if (cmp == 0)
                        return *l;
@@ -860,13 +1578,74 @@ cannot nest, so a declaration while a name is in-scope is an error.
 ###### ast
        struct variable {
                struct variable *previous;
-               struct value val;
+               struct type *type;
                struct binding *name;
                struct exec *where_decl;// where name was declared
                struct exec *where_set; // where type was set
                ## variable fields
        };
 
+The parser will want to be able to free a variable, but as this will
+also be a reference to something stored in the parse context, there is
+not action needed.
+
+###### ast functions
+       void free_variable(struct variable *v)
+       {
+       }
+
+When a scope closes, the values of the variables might need to be freed.
+This happens in the context of some `struct exec` and each `exec` will
+need to know which variables need to be freed when it completes.  To
+improve visibility, we add a comment when printing any `exec` that
+embodies a scope to list the variables that must be freed when it ends.
+
+####### exec fields
+       struct variable *to_free;
+
+####### variable fields
+       struct exec *cleanup_exec;
+       struct variable *next_free;
+
+####### interp exec cleanup
+       {
+               struct variable *v;
+               for (v = e->to_free; v; v = v->next_free) {
+                       struct value *val = var_value(c, v);
+                       free_value(v->type, val);
+               }
+       }
+
+###### print exec extras
+       if (e->to_free) {
+               struct variable *v;
+               do_indent(indent, "/* FREE");
+               for (v = e->to_free; v; v = v->next_free) {
+                       printf(" %.*s", v->name->name.len, v->name->name.txt);
+                       printf("[%d,%d]", v->scope_start, v->scope_end);
+                       if (v->frame_pos >= 0)
+                               printf("(%d+%d)", v->frame_pos,
+                                      v->type ? v->type->size:0);
+               }
+               printf(" */\n");
+       }
+
+###### ast functions
+       static void variable_unlink_exec(struct variable *v)
+       {
+               struct variable **vp;
+               if (!v->cleanup_exec)
+                       return;
+               for (vp = &v->cleanup_exec->to_free;
+                   *vp; vp = &(*vp)->next_free) {
+                       if (*vp != v)
+                               continue;
+                       *vp = v->next_free;
+                       v->cleanup_exec = NULL;
+                       break;
+               }
+       }
+
 While the naming seems strange, we include local constants in the
 definition of variables.  A name declared `var := value` can
 subsequently be changed, but a name declared `var ::= value` cannot -
@@ -877,7 +1656,7 @@ it is constant
 
 Scopes in parallel branches can be partially merged.  More
 specifically, if a given name is declared in both branches of an
-if/else then it's scope is a candidate for merging.  Similarly if
+if/else then its scope is a candidate for merging.  Similarly if
 every branch of an exhaustive switch (e.g. has an "else" clause)
 declares a given name, then the scopes from the branches are
 candidates for merging.
@@ -912,12 +1691,18 @@ for scoping.  When a new scope is opened, a new frame is pushed and
 the child-count of the parent frame is incremented.  This child-count
 is used to distinguish between the first of a set of parallel scopes,
 in which declared variables must not be in scope, and subsequent
-branches, whether they must already be conditionally scoped.
+branches, whether they may already be conditionally scoped.
+
+We need a total ordering of scopes so we can easily compare to variables
+to see if they are concurrently in scope.  To achieve this we record a
+`scope_count` which is actually a count of both beginnings and endings
+of scopes.  Then each variable has a record of the scope count where it
+enters scope, and where it leaves.
 
 To push a new frame *before* any code in the frame is parsed, we need a
 grammar reduction.  This is most easily achieved with a grammar
 element which derives the empty string, and creates the new scope when
-it is recognized.  This can be placed, for example, between a keyword
+it is recognised.  This can be placed, for example, between a keyword
 like "if" and the code following it.
 
 ###### ast
@@ -928,8 +1713,12 @@ like "if" and the code following it.
 
 ###### parse context
        int scope_depth;
+       int scope_count;
        struct scope *scope_stack;
 
+###### variable fields
+       int scope_start, scope_end;
+
 ###### ast functions
        static void scope_pop(struct parse_context *c)
        {
@@ -938,6 +1727,7 @@ like "if" and the code following it.
                c->scope_stack = s->parent;
                free(s);
                c->scope_depth -= 1;
+               c->scope_count += 1;
        }
 
        static void scope_push(struct parse_context *c)
@@ -948,13 +1738,13 @@ like "if" and the code following it.
                s->parent = c->scope_stack;
                c->scope_stack = s;
                c->scope_depth += 1;
+               c->scope_count += 1;
        }
 
 ###### Grammar
 
        $void
-       OpenScope -> ${ scope_push(config2context(config)); }$
-
+       OpenScope -> ${ scope_push(c); }$
 
 Each variable records a scope depth and is in one of four states:
 
@@ -975,18 +1765,20 @@ Each variable records a scope depth and is in one of four states:
   enclosed the declaration, and that has closed.
 
 - "conditionally in scope".  The "in scope" block and all parallel
-  scopes have closed, and no further mention of the name has been
-  seen.  This state includes a secondary nest depth which records the
-  outermost scope seen since the variable became conditionally in
-  scope.  If a use of the name is found, the variable becomes "in
-  scope" and that secondary depth becomes the recorded scope depth.
-  If the name is declared as a new variable, the old variable becomes
-  "out of scope" and the recorded scope depth stays unchanged.
+  scopes have closed, and no further mention of the name has been seen.
+  This state includes a secondary nest depth (`min_depth`) which records
+  the outermost scope seen since the variable became conditionally in
+  scope.  If a use of the name is found, the variable becomes "in scope"
+  and that secondary depth becomes the recorded scope depth.  If the
+  name is declared as a new variable, the old variable becomes "out of
+  scope" and the recorded scope depth stays unchanged.
 
 - "out of scope".  The variable is neither in scope nor conditionally
   in scope.  It is permanently out of scope now and can be removed from
-  the "in scope" stack.
-
+  the "in scope" stack.  When a variable becomes out-of-scope it is
+  moved to a separate list (`out_scope`) of variables which have fully
+  known scope.  This will be used at the end of each function to assign
+  each variable a place in the stack frame.
 
 ###### variable fields
        int depth, min_depth;
@@ -996,14 +1788,26 @@ Each variable records a scope depth and is in one of four states:
 ###### parse context
 
        struct variable *in_scope;
+       struct variable *out_scope;
 
 All variables with the same name are linked together using the
-'previous' link.  Those variable that have
-been affirmatively merged all have a 'merged' pointer that points to
-one primary variable - the most recently declared instance. When
-merging variables, we need to also adjust the 'merged' pointer on any
-other variables that had previously been merged with the one that will
-no longer be primary.
+'previous' link.  Those variable that have been affirmatively merged all
+have a 'merged' pointer that points to one primary variable - the most
+recently declared instance.  When merging variables, we need to also
+adjust the 'merged' pointer on any other variables that had previously
+been merged with the one that will no longer be primary.
+
+A variable that is no longer the most recent instance of a name may
+still have "pending" scope, if it might still be merged with most
+recent instance.  These variables don't really belong in the
+"in_scope" list, but are not immediately removed when a new instance
+is found.  Instead, they are detected and ignored when considering the
+list of in_scope names.
+
+The storage of the value of a variable will be described later.  For now
+we just need to know that when a variable goes out of scope, it might
+need to be freed.  For this we need to be able to find it, so assume that
+`var_value()` will provide that.
 
 ###### variable fields
        struct variable *merged;
@@ -1014,20 +1818,26 @@ no longer be primary.
        {
                struct variable *v;
 
-               if (primary->merged)
-                       // shouldn't happen
-                       primary = primary->merged;
+               primary = primary->merged;
 
                for (v = primary->previous; v; v=v->previous)
                        if (v == secondary || v == secondary->merged ||
                            v->merged == secondary ||
-                           (v->merged && v->merged == secondary->merged)) {
+                           v->merged == secondary->merged) {
                                v->scope = OutScope;
                                v->merged = primary;
+                               if (v->scope_start < primary->scope_start)
+                                       primary->scope_start = v->scope_start;
+                               if (v->scope_end > primary->scope_end)
+                                       primary->scope_end = v->scope_end;      // NOTEST
+                               variable_unlink_exec(v);
                        }
        }
 
-###### free context vars
+###### forward decls
+       static struct value *var_value(struct parse_context *c, struct variable *v);
+
+###### free global vars
 
        while (context.varlist) {
                struct binding *b = context.varlist;
@@ -1035,43 +1845,52 @@ no longer be primary.
                context.varlist = b->next;
                free(b);
                while (v) {
-                       struct variable *t = v;
+                       struct variable *next = v->previous;
 
-                       v = t->previous;
-                       free_value(t->val);
-                       free(t);
+                       if (v->global && v->frame_pos >= 0) {
+                               free_value(v->type, var_value(&context, v));
+                               if (v->depth == 0 && v->type->free == function_free)
+                                       // This is a function constant
+                                       free_exec(v->where_decl);
+                       }
+                       free(v);
+                       v = next;
                }
        }
 
 #### Manipulating Bindings
 
-When a name is conditionally visible, a new declaration discards the
-old binding - the condition lapses.  Conversely a usage of the name
-affirms the visibility and extends it to the end of the containing
-block - i.e. the block that contains both the original declaration and
-the latest usage.  This is determined from `min_depth`.  When a
-conditionally visible variable gets affirmed like this, it is also
-merged with other conditionally visible variables with the same name.
-
-When we parse a variable declaration we either signal an error if the
+When a name is conditionally visible, a new declaration discards the old
+binding - the condition lapses.  Similarly when we reach the end of a
+function (outermost non-global scope) any conditional scope must lapse.
+Conversely a usage of the name affirms the visibility and extends it to
+the end of the containing block - i.e.  the block that contains both the
+original declaration and the latest usage.  This is determined from
+`min_depth`.  When a conditionally visible variable gets affirmed like
+this, it is also merged with other conditionally visible variables with
+the same name.
+
+When we parse a variable declaration we either report an error if the
 name is currently bound, or create a new variable at the current nest
 depth if the name is unbound or bound to a conditionally scoped or
 pending-scope variable.  If the previous variable was conditionally
 scoped, it and its homonyms becomes out-of-scope.
 
-When we parse a variable reference (including non-declarative
-assignment) we signal an error if the name is not bound or is bound to
+When we parse a variable reference (including non-declarative assignment
+"foo = bar") we report an error if the name is not bound or is bound to
 a pending-scope variable; update the scope if the name is bound to a
 conditionally scoped variable; or just proceed normally if the named
 variable is in scope.
 
 When we exit a scope, any variables bound at this level are either
-marked out of scope or pending-scoped, depending on whether the
-scope was sequential or parallel.
+marked out of scope or pending-scoped, depending on whether the scope
+was sequential or parallel.  Here a "parallel" scope means the "then"
+or "else" part of a conditional, or any "case" or "else" branch of a
+switch.  Other scopes are "sequential".
 
 When exiting a parallel scope we check if there are any variables that
 were previously pending and are still visible. If there are, then
-there weren't redeclared in the most recent scope, so they cannot be
+they weren't redeclared in the most recent scope, so they cannot be
 merged and must become out-of-scope.  If it is not the first of
 parallel scopes (based on `child_count`), we check that there was a
 previous binding that is still pending-scope.  If there isn't, the new
@@ -1084,7 +1903,7 @@ we need to mark all pending-scope variable as out-of-scope.  Otherwise
 all pending-scope variables become conditionally scoped.
 
 ###### ast
-       enum closetype { CloseSequential, CloseParallel, CloseElse };
+       enum closetype { CloseSequential, CloseFunction, CloseParallel, CloseElse };
 
 ###### ast functions
 
@@ -1109,11 +1928,13 @@ all pending-scope variables become conditionally scoped.
                v->previous = b->var;
                b->var = v;
                v->name = b;
+               v->merged = v;
                v->min_depth = v->depth = c->scope_depth;
                v->scope = InScope;
                v->in_scope = c->in_scope;
+               v->scope_start = c->scope_count;
                c->in_scope = v;
-               v->val = val_init(NULL);
+               ## variable init
                return v;
        }
 
@@ -1126,7 +1947,7 @@ all pending-scope variables become conditionally scoped.
                switch (v ? v->scope : OutScope) {
                case OutScope:
                case PendingScope:
-                       /* Signal an error - once that is possible */
+                       /* Caller will report the error */
                        return NULL;
                case CondScope:
                        /* All CondScope variables of this name need to be merged
@@ -1145,15 +1966,56 @@ all pending-scope variables become conditionally scoped.
                return v;
        }
 
-       static void var_block_close(struct parse_context *c, enum closetype ct)
+       static int var_refile(struct parse_context *c, struct variable *v)
+       {
+               /* Variable just went out of scope.  Add it to the out_scope
+                * list, sorted by ->scope_start
+                */
+               struct variable **vp = &c->out_scope;
+               while ((*vp) && (*vp)->scope_start < v->scope_start)
+                       vp = &(*vp)->in_scope;
+               v->in_scope = *vp;
+               *vp = v;
+               return 0;
+       }
+
+       static void var_block_close(struct parse_context *c, enum closetype ct,
+                                   struct exec *e)
        {
-               /* close of all variables that are in_scope */
+               /* Close off all variables that are in_scope.
+                * Some variables in c->scope may already be not-in-scope,
+                * such as when a PendingScope variable is hidden by a new
+                * variable with the same name.
+                * So we check for v->name->var != v and drop them.
+                * If we choose to make a variable OutScope, we drop it
+                * immediately too.
+                */
                struct variable *v, **vp, *v2;
 
                scope_pop(c);
                for (vp = &c->in_scope;
-                    v = *vp, v && v->depth > c->scope_depth && v->min_depth > c->scope_depth;
-                    ) {
+                    (v = *vp) && v->min_depth > c->scope_depth;
+                    (v->scope == OutScope || v->name->var != v)
+                    ? (*vp =  v->in_scope, var_refile(c, v))
+                    : ( vp = &v->in_scope, 0)) {
+                       v->min_depth = c->scope_depth;
+                       if (v->name->var != v)
+                               /* This is still in scope, but we haven't just
+                                * closed the scope.
+                                */
+                               continue;
+                       v->min_depth = c->scope_depth;
+                       if (v->scope == InScope)
+                               v->scope_end = c->scope_count;
+                       if (v->scope == InScope && e && !v->global) {
+                               /* This variable gets cleaned up when
+                                * 'e' finishes
+                                */
+                               variable_unlink_exec(v);
+                               v->cleanup_exec = e;
+                               v->next_free = e->to_free;
+                               e->to_free = v;
+                       }
                        switch (ct) {
                        case CloseElse:
                        case CloseParallel: /* handle PendingScope */
@@ -1161,17 +2023,18 @@ all pending-scope variables become conditionally scoped.
                                case InScope:
                                case CondScope:
                                        if (c->scope_stack->child_count == 1)
+                                               /* first among parallel branches */
                                                v->scope = PendingScope;
                                        else if (v->previous &&
                                                 v->previous->scope == PendingScope)
+                                               /* all previous branches used name */
                                                v->scope = PendingScope;
-                                       else if (v->val.type == Tlabel)
-                                               v->scope = PendingScope;
-                                       else if (v->name->var == v)
+                                       else
                                                v->scope = OutScope;
                                        if (ct == CloseElse) {
-                                               /* All Pending variables with this name
-                                                * are now Conditional */
+                                               /* All Pending variables with
+                                                * this name are now Conditional
+                                                */
                                                for (v2 = v;
                                                     v2 && v2->scope == PendingScope;
                                                     v2 = v2->previous)
@@ -1179,18 +2042,25 @@ all pending-scope variables become conditionally scoped.
                                        }
                                        break;
                                case PendingScope:
-                                       for (v2 = v;
-                                            v2 && v2->scope == PendingScope;
-                                            v2 = v2->previous)
-                                               if (v2->val.type != Tlabel)
-                                                       v2->scope = OutScope;
-                                       break;
-                               case OutScope: break;
+                                       /* Not possible as it would require
+                                        * parallel scope to be nested immediately
+                                        * in a parallel scope, and that never
+                                        * happens.
+                                        */                     // NOTEST
+                               case OutScope:
+                                       /* Not possible as we already tested for
+                                        * OutScope
+                                        */
+                                       abort();                // NOTEST
                                }
                                break;
+                       case CloseFunction:
+                               if (v->scope == CondScope)
+                                       /* Condition cannot continue past end of
+                                        * function */
+                                       v->scope = InScope;
+                               /* fallthrough */
                        case CloseSequential:
-                               if (v->val.type == Tlabel)
-                                       v->scope = PendingScope;
                                switch (v->scope) {
                                case InScope:
                                        v->scope = OutScope;
@@ -1204,623 +2074,1989 @@ all pending-scope variables become conditionally scoped.
                                        for (v2 = v;
                                             v2 && v2->scope == PendingScope;
                                             v2 = v2->previous)
-                                               if (v2->val.type == Tlabel) {
-                                                       v2->scope = CondScope;
-                                                       v2->min_depth = c->scope_depth;
-                                               } else
-                                                       v2->scope = OutScope;
+                                               v2->scope = OutScope;
                                        break;
                                case CondScope:
                                case OutScope: break;
                                }
                                break;
                        }
-                       if (v->scope == OutScope)
-                               *vp = v->in_scope;
-                       else
-                               vp = &v->in_scope;
                }
        }
 
-### Executables
-
-Executables can be lots of different things.  In many cases an
-executable is just an operation combined with one or two other
-executables.  This allows for expressions and lists etc.  Other times
-an executable is something quite specific like a constant or variable
-name.  So we define a `struct exec` to be a general executable with a
-type, and a `struct binode` which is a subclass of `exec`, forms a
-node in a binary tree, and holds an operation. There will be other
-subclasses, and to access these we need to be able to `cast` the
-`exec` into the various other types.
-
-###### macros
-       #define cast(structname, pointer) ({            \
-               const typeof( ((struct structname *)0)->type) *__mptr = &(pointer)->type; \
-               if (__mptr && *__mptr != X##structname) abort();                \
-               (struct structname *)( (char *)__mptr);})
+#### Storing Values
 
-       #define new(structname) ({                                              \
-               struct structname *__ptr = ((struct structname *)calloc(1,sizeof(struct structname))); \
-               __ptr->type = X##structname;                                            \
-               __ptr->line = -1; __ptr->column = -1;                                   \
-               __ptr;})
+The value of a variable is stored separately from the variable, on an
+analogue of a stack frame.  There are (currently) two frames that can be
+active.  A global frame which currently only stores constants, and a
+stacked frame which stores local variables.  Each variable knows if it
+is global or not, and what its index into the frame is.
 
-       #define new_pos(structname, token) ({                                           \
-               struct structname *__ptr = ((struct structname *)calloc(1,sizeof(struct structname))); \
-               __ptr->type = X##structname;                                            \
-               __ptr->line = token.line; __ptr->column = token.col;                    \
-               __ptr;})
+Values in the global frame are known immediately they are relevant, so
+the frame needs to be reallocated as it grows so it can store those
+values.  The local frame doesn't get values until the interpreted phase
+is started, so there is no need to allocate until the size is known.
 
-###### ast
-       enum exec_types {
-               Xbinode,
-               ## exec type
-       };
-       struct exec {
-               enum exec_types type;
-               int line, column;
-       };
-       struct binode {
-               struct exec;
-               enum Btype {
-                       ## Binode types
-               } op;
-               struct exec *left, *right;
-       };
+We initialise the `frame_pos` to an impossible value, so that we can
+tell if it was set or not later.
 
-###### ast functions
+###### variable fields
+       short frame_pos;
+       short global;
 
-       static int __fput_loc(struct exec *loc, FILE *f)
-       {
-               if (loc->line >= 0) {
-                       fprintf(f, "%d:%d: ", loc->line, loc->column);
-                       return 1;
-               }
-               if (loc->type == Xbinode)
-                       return __fput_loc(cast(binode,loc)->left, f) ||
-                              __fput_loc(cast(binode,loc)->right, f);
-               return 0;
-       }
-       static void fput_loc(struct exec *loc, FILE *f)
-       {
-               if (!__fput_loc(loc, f))
-                       fprintf(f, "??:??: ");
-       }
+###### variable init
+       v->frame_pos = -1;
 
-Each different type of `exec` node needs a number of functions
-defined, a bit like methods.  We must be able to be able to free it,
-print it, analyse it and execute it.  Once we have specific `exec`
-types we will need to parse them too.  Let's take this a bit more
-slowly.
+###### parse context
 
-#### Freeing
+       short global_size, global_alloc;
+       short local_size;
+       void *global, *local;
 
-The parser generator requires a `free_foo` function for each struct
-that stores attributes and they will be `exec`s and subtypes there-of.
-So we need `free_exec` which can handle all the subtypes, and we need
-`free_binode`.
+###### forward decls
+       static struct value *global_alloc(struct parse_context *c, struct type *t,
+                                         struct variable *v, struct value *init);
 
 ###### ast functions
 
-       static void free_binode(struct binode *b)
+       static struct value *var_value(struct parse_context *c, struct variable *v)
        {
-               if (!b)
-                       return;
-               free_exec(b->left);
-               free_exec(b->right);
-               free(b);
+               if (!v->global) {
+                       if (!c->local || !v->type)
+                               return NULL;    // NOTEST
+                       if (v->frame_pos + v->type->size > c->local_size) {
+                               printf("INVALID frame_pos\n");  // NOTEST
+                               exit(2);                        // NOTEST
+                       }
+                       return c->local + v->frame_pos;
+               }
+               if (c->global_size > c->global_alloc) {
+                       int old = c->global_alloc;
+                       c->global_alloc = (c->global_size | 1023) + 1024;
+                       c->global = realloc(c->global, c->global_alloc);
+                       memset(c->global + old, 0, c->global_alloc - old);
+               }
+               return c->global + v->frame_pos;
        }
 
-###### core functions
-       static void free_exec(struct exec *e)
+       static struct value *global_alloc(struct parse_context *c, struct type *t,
+                                         struct variable *v, struct value *init)
        {
-               if (!e)
-                       return;
-               switch(e->type) {
-                       ## free exec cases
-               }
-       }
+               struct value *ret;
+               struct variable scratch;
 
-###### forward decls
+               if (t->prepare_type)
+                       t->prepare_type(c, t, 1);       // NOTEST
 
-       static void free_exec(struct exec *e);
+               if (c->global_size & (t->align - 1))
+                       c->global_size = (c->global_size + t->align) & ~(t->align-1);
+               if (!v) {
+                       v = &scratch;
+                       v->type = t;
+               }
+               v->frame_pos = c->global_size;
+               v->global = 1;
+               c->global_size += v->type->size;
+               ret = var_value(c, v);
+               if (init)
+                       memcpy(ret, init, t->size);
+               else
+                       val_init(t, ret);       // NOTEST
+               return ret;
+       }
 
-###### free exec cases
-       case Xbinode: free_binode(cast(binode, e)); break;
+As global values are found -- struct field initializers, labels etc --
+`global_alloc()` is called to record the value in the global frame.
 
-#### Printing
+When the program is fully parsed, each function is analysed, we need to
+walk the list of variables local to that function and assign them an
+offset in the stack frame.  For this we have `scope_finalize()`.
 
-Printing an `exec` requires that we know the current indent level for
-printing line-oriented components.  As will become clear later, we
-also want to know what sort of bracketing to use.
+We keep the stack from dense by re-using space for between variables
+that are not in scope at the same time.  The `out_scope` list is sorted
+by `scope_start` and as we process a varible, we move it to an FIFO
+stack.  For each variable we consider, we first discard any from the
+stack anything that went out of scope before the new variable came in.
+Then we place the new variable just after the one at the top of the
+stack.
 
 ###### ast functions
 
-       static void do_indent(int i, char *str)
-       {
-               while (i--)
-                       printf("    ");
-               printf("%s", str);
-       }
-
-###### core functions
-       static void print_binode(struct binode *b, int indent, int bracket)
-       {
-               struct binode *b2;
-               switch(b->op) {
-               ## print binode cases
-               }
-       }
-
-       static void print_exec(struct exec *e, int indent, int bracket)
+       static void scope_finalize(struct parse_context *c, struct type *ft)
        {
-               if (!e)
-                       return;
-               switch (e->type) {
-               case Xbinode:
-                       print_binode(cast(binode, e), indent, bracket); break;
-               ## print exec cases
+               int size = ft->function.local_size;
+               struct variable *next = ft->function.scope;
+               struct variable *done = NULL;
+
+               while (next) {
+                       struct variable *v = next;
+                       struct type *t = v->type;
+                       int pos;
+                       next = v->in_scope;
+                       if (v->merged != v)
+                               continue;
+                       if (!t)
+                               continue;       // NOTEST
+                       if (v->frame_pos >= 0)
+                               continue;
+                       while (done && done->scope_end < v->scope_start)
+                               done = done->in_scope;
+                       if (done)
+                               pos = done->frame_pos + done->type->size;
+                       else
+                               pos = ft->function.local_size;
+                       if (pos & (t->align - 1))
+                               pos = (pos + t->align) & ~(t->align-1);
+                       v->frame_pos = pos;
+                       if (size < pos + v->type->size)
+                               size = pos + v->type->size;
+                       v->in_scope = done;
+                       done = v;
                }
+               c->out_scope = NULL;
+               ft->function.local_size = size;
        }
 
-###### forward decls
+###### free context storage
+       free(context.global);
 
-       static void print_exec(struct exec *e, int indent, int bracket);
+#### Variables as executables
 
-#### Analysing
+Just as we used a `val` to wrap a value into an `exec`, we similarly
+need a `var` to wrap a `variable` into an exec.  While each `val`
+contained a copy of the value, each `var` holds a link to the variable
+because it really is the same variable no matter where it appears.
+When a variable is used, we need to remember to follow the `->merged`
+link to find the primary instance.
 
-As discussed, analysis involves propagating type requirements around
-the program and looking for errors.
+When a variable is declared, it may or may not be given an explicit
+type.  We need to record which so that we can report the parsed code
+correctly.
 
-So `propagate_types` is passed an expected type (being a `struct type`
-pointer together with some `val_rules` flags) that the `exec` is
-expected to return, and returns the type that it does return, either
-of which can be `NULL` signifying "unknown".  An `ok` flag is passed
-by reference. It is set to `0` when an error is found, and `2` when
-any change is made.  If it remains unchanged at `1`, then no more
-propagation is needed.
+###### exec type
+       Xvar,
 
-###### core functions
+###### ast
+       struct var {
+               struct exec;
+               struct variable *var;
+       };
+
+###### variable fields
+       int explicit_type;
+
+###### Grammar
+
+       $TERM : ::
+
+       $*var
+       VariableDecl -> IDENTIFIER : ${ {
+               struct variable *v = var_decl(c, $1.txt);
+               $0 = new_pos(var, $1);
+               $0->var = v;
+               if (v)
+                       v->where_decl = $0;
+               else {
+                       v = var_ref(c, $1.txt);
+                       $0->var = v;
+                       type_err(c, "error: variable '%v' redeclared",
+                                $0, NULL, 0, NULL);
+                       type_err(c, "info: this is where '%v' was first declared",
+                                v->where_decl, NULL, 0, NULL);
+               }
+       } }$
+       | IDENTIFIER :: ${ {
+               struct variable *v = var_decl(c, $1.txt);
+               $0 = new_pos(var, $1);
+               $0->var = v;
+               if (v) {
+                       v->where_decl = $0;
+                       v->constant = 1;
+               } else {
+                       v = var_ref(c, $1.txt);
+                       $0->var = v;
+                       type_err(c, "error: variable '%v' redeclared",
+                                $0, NULL, 0, NULL);
+                       type_err(c, "info: this is where '%v' was first declared",
+                                v->where_decl, NULL, 0, NULL);
+               }
+       } }$
+       | IDENTIFIER : Type ${ {
+               struct variable *v = var_decl(c, $1.txt);
+               $0 = new_pos(var, $1);
+               $0->var = v;
+               if (v) {
+                       v->where_decl = $0;
+                       v->where_set = $0;
+                       v->type = $<Type;
+                       v->explicit_type = 1;
+               } else {
+                       v = var_ref(c, $1.txt);
+                       $0->var = v;
+                       type_err(c, "error: variable '%v' redeclared",
+                                $0, NULL, 0, NULL);
+                       type_err(c, "info: this is where '%v' was first declared",
+                                v->where_decl, NULL, 0, NULL);
+               }
+       } }$
+       | IDENTIFIER :: Type ${ {
+               struct variable *v = var_decl(c, $1.txt);
+               $0 = new_pos(var, $1);
+               $0->var = v;
+               if (v) {
+                       v->where_decl = $0;
+                       v->where_set = $0;
+                       v->type = $<Type;
+                       v->constant = 1;
+                       v->explicit_type = 1;
+               } else {
+                       v = var_ref(c, $1.txt);
+                       $0->var = v;
+                       type_err(c, "error: variable '%v' redeclared",
+                                $0, NULL, 0, NULL);
+                       type_err(c, "info: this is where '%v' was first declared",
+                                v->where_decl, NULL, 0, NULL);
+               }
+       } }$
+
+       $*exec
+       Variable -> IDENTIFIER ${ {
+               struct variable *v = var_ref(c, $1.txt);
+               $0 = new_pos(var, $1);
+               if (v == NULL) {
+                       /* This might be a global const or a label
+                        * Allocate a var with impossible type Tnone,
+                        * which will be adjusted when we find out what it is,
+                        * or will trigger an error.
+                        */
+                       v = var_decl(c, $1.txt);
+                       if (v) {
+                               v->type = Tnone;
+                               v->where_decl = $0;
+                               v->where_set = $0;
+                       }
+               }
+               cast(var, $0)->var = v;
+       } }$
+
+###### print exec cases
+       case Xvar:
+       {
+               struct var *v = cast(var, e);
+               if (v->var) {
+                       struct binding *b = v->var->name;
+                       printf("%.*s", b->name.len, b->name.txt);
+               }
+               break;
+       }
+
+###### format cases
+       case 'v':
+               if (loc && loc->type == Xvar) {
+                       struct var *v = cast(var, loc);
+                       if (v->var) {
+                               struct binding *b = v->var->name;
+                               fprintf(stderr, "%.*s", b->name.len, b->name.txt);
+                       } else
+                               fputs("???", stderr);   // NOTEST
+               } else
+                       fputs("NOTVAR", stderr);        // NOTEST
+               break;
+
+###### propagate exec cases
+
+       case Xvar:
+       {
+               struct var *var = cast(var, prog);
+               struct variable *v = var->var;
+               if (!v) {
+                       type_err(c, "%d:BUG: no variable!!", prog, NULL, 0, NULL); // NOTEST
+                       return Tnone;                                   // NOTEST
+               }
+               v = v->merged;
+               if (v->type == Tnone && v->where_decl == prog)
+                       type_err(c, "error: variable used but not declared: %v",
+                                prog, NULL, 0, NULL);
+               if (v->type == NULL) {
+                       if (type && !(*perr & Efail)) {
+                               v->type = type;
+                               v->where_set = prog;
+                               *perr |= Eretry;
+                       }
+               } else if (!type_compat(type, v->type, rules)) {
+                       type_err(c, "error: expected %1 but variable '%v' is %2", prog,
+                                type, rules, v->type);
+                       type_err(c, "info: this is where '%v' was set to %1", v->where_set,
+                                v->type, rules, NULL);
+               }
+               if (!v->global || v->frame_pos < 0)
+                       *perr |= Eruntime;
+               if (v->constant)
+                       *perr |= Econst;
+               return v->type;
+       }
+
+###### interp exec cases
+       case Xvar:
+       {
+               struct var *var = cast(var, e);
+               struct variable *v = var->var;
+
+               v = v->merged;
+               lrv = var_value(c, v);
+               rvtype = v->type;
+               break;
+       }
+
+###### ast functions
+
+       static void free_var(struct var *v)
+       {
+               free(v);
+       }
+
+###### free exec cases
+       case Xvar: free_var(cast(var, e)); break;
+
+### Complex types
+
+Now that we have the shape of the interpreter in place we can add some
+complex types and connected them in to the data structures and the
+different phases of parse, analyse, print, interpret.
+
+Being "complex" the language will naturally have syntax to access
+specifics of objects of these types.  These will fit into the grammar as
+"Terms" which are the things that are combined with various operators to
+form an "Expression".  Where a Term is formed by some operation on another
+Term, the subordinate Term will always come first, so for example a
+member of an array will be expressed as the Term for the array followed
+by an index in square brackets.  The strict rule of using postfix
+operations makes precedence irrelevant within terms.  To provide a place
+to put the grammar for terms of each type, we will start out by
+introducing the "Term" grammar production, with contains at least a
+simple "Value" (to be explained later).
+
+We also take this opportunity to introduce the "ExpressionsList" which
+is a simple comma-separated list of expressions - it may be used in
+various places.
+
+###### declare terminals
+       $TERM ,
+
+###### Grammar
+       $*exec
+       Term ->  Value ${ $0 = $<1; }$
+       | Variable ${ $0 = $<1; }$
+       ## term grammar
+
+       $*binode
+       ExpressionList -> ExpressionList , Expression ${
+               $0 = new(binode);
+               $0->op = List;
+               $0->left = $<1;
+               $0->right = $<3;
+       }$
+       | Expression ${
+               $0 = new(binode);
+               $0->op = List;
+               $0->left = NULL;
+               $0->right = $<1;
+       }$
+
+Thus far the complex types we have are arrays, structs, functions and
+references.
+
+#### Arrays
+
+Arrays can be declared by giving a size and a type, as `[size]type' so
+`freq:[26]number` declares `freq` to be an array of 26 numbers.  The
+size can be either a literal number, or a named constant.  Some day an
+arbitrary expression will be supported.
+
+As a formal parameter to a function, the array can be declared with
+unknown size `name:[]string`.  This is currently only supported for the
+"argv" parameter to "main" but will be extended more generally in a
+later version of the language.  The length of this array - or any array
+- can be found with the "[]" postfix operator.
+
+Arrays cannot be assigned.  When reference are extend to allow array
+slices which can refer to part or all of an array the assignment
+syntax will create a slice.  For now, an array can only ever be
+referenced by the name it is declared with.  It is likely that a
+"`copy`" primitive will eventually be defined which can be used to make a
+copy of an array with controllable recursive depth.
+
+For now we have two sorts of array, those with fixed size either because
+it is given as a literal number or because it is a struct member (which
+cannot have a runtime-changing size), and those with a size that is
+determined at runtime - local variables with a const size.  The former
+have their size calculated at parse time, the latter at run time.
+
+For the latter type, the `size` field of the type is the size of a
+pointer, and the array is reallocated every time it comes into scope.
+
+We differentiate struct fields with a const size from local variables
+with a const size by whether they are prepared at parse time or not.
+
+###### type union fields
+
+       struct {
+               int unspec;     // size is unspecified - vsize must be set.
+               short size;
+               short static_size;
+               struct variable *vsize;
+               struct type *member;
+       } array;
+
+###### value union fields
+       void *array;  // used if not static_size
+
+###### value functions
+
+       static int array_prepare_type(struct parse_context *c, struct type *type,
+                                      int parse_time)
+       {
+               struct value *vsize;
+               mpz_t q;
+               if (type->array.static_size)
+                       return 1;       // NOTEST - guard against reentry
+               if (type->array.unspec && parse_time)
+                       return 1;       // NOTEST - unspec is still incomplete
+               if (parse_time && type->array.vsize && !type->array.vsize->global)
+                       return 1;       // NOTEST - should be impossible
+
+               if (type->array.vsize) {
+                       vsize = var_value(c, type->array.vsize);
+                       if (!vsize)
+                               return 1;       // NOTEST - should be impossible
+                       mpz_init(q);
+                       mpz_tdiv_q(q, mpq_numref(vsize->num), mpq_denref(vsize->num));
+                       type->array.size = mpz_get_si(q);
+                       mpz_clear(q);
+               }
+               if (!parse_time)
+                       return 1;
+               if (type->array.member->size <= 0)
+                       return 0;       // NOTEST - error caught before here
+
+               type->array.static_size = 1;
+               type->size = type->array.size * type->array.member->size;
+               type->align = type->array.member->align;
+
+               return 1;
+       }
+
+       static void array_init(struct type *type, struct value *val)
+       {
+               int i;
+               void *ptr = val->ptr;
+
+               if (!val)
+                       return;                         // NOTEST
+               if (!type->array.static_size) {
+                       val->array = calloc(type->array.size,
+                                           type->array.member->size);
+                       ptr = val->array;
+               }
+               for (i = 0; i < type->array.size; i++) {
+                       struct value *v;
+                       v = (void*)ptr + i * type->array.member->size;
+                       val_init(type->array.member, v);
+               }
+       }
+
+       static void array_free(struct type *type, struct value *val)
+       {
+               int i;
+               void *ptr = val->ptr;
+
+               if (!type->array.static_size)
+                       ptr = val->array;
+               for (i = 0; i < type->array.size; i++) {
+                       struct value *v;
+                       v = (void*)ptr + i * type->array.member->size;
+                       free_value(type->array.member, v);
+               }
+               if (!type->array.static_size)
+                       free(ptr);
+       }
+
+       static int array_compat(struct type *require, struct type *have,
+                               enum val_rules rules)
+       {
+               if (have->compat != require->compat)
+                       return 0;
+               /* Both are arrays, so we can look at details */
+               if (!type_compat(require->array.member, have->array.member, 0))
+                       return 0;
+               if (have->array.unspec && require->array.unspec &&
+                   have->array.size != require->array.size)
+                       return 0;       // NOTEST
+               if (have->array.unspec || require->array.unspec)
+                       return 1;
+               if (require->array.vsize == NULL && have->array.vsize == NULL)
+                       return require->array.size == have->array.size;
+
+               return require->array.vsize == have->array.vsize;
+       }
+
+       static void array_print_type(struct type *type, FILE *f)
+       {
+               fputs("[", f);
+               if (type->array.vsize) {
+                       struct binding *b = type->array.vsize->name;
+                       fprintf(f, "%.*s%s]", b->name.len, b->name.txt,
+                               type->array.unspec ? "::" : "");
+               } else if (type->array.size)
+                       fprintf(f, "%d]", type->array.size);
+               else
+                       fprintf(f, "]");
+               type_print(type->array.member, f);
+       }
+
+       static struct type array_prototype = {
+               .init = array_init,
+               .prepare_type = array_prepare_type,
+               .print_type = array_print_type,
+               .compat = array_compat,
+               .free = array_free,
+               .size = sizeof(void*),
+               .align = sizeof(void*),
+       };
+
+###### declare terminals
+       $TERM [ ]
+
+###### type grammar
+
+       | [ NUMBER ] Type ${ {
+               char tail[3];
+               mpq_t num;
+               struct type *t;
+               int elements = 0;
+
+               if (number_parse(num, tail, $2.txt) == 0)
+                       tok_err(c, "error: unrecognised number", &$2);
+               else if (tail[0]) {
+                       tok_err(c, "error: unsupported number suffix", &$2);
+                       mpq_clear(num);
+               } else {
+                       elements = mpz_get_ui(mpq_numref(num));
+                       if (mpz_cmp_ui(mpq_denref(num), 1) != 0) {
+                               tok_err(c, "error: array size must be an integer",
+                                       &$2);
+                       } else if (mpz_cmp_ui(mpq_numref(num), 1UL << 30) >= 0)
+                               tok_err(c, "error: array size is too large",
+                                       &$2);
+                       mpq_clear(num);
+               }
+
+               $0 = t = add_anon_type(c, &array_prototype, "array[%d]", elements );
+               t->array.size = elements;
+               t->array.member = $<4;
+               t->array.vsize = NULL;
+       } }$
+
+       | [ IDENTIFIER ] Type ${ {
+               struct variable *v = var_ref(c, $2.txt);
+
+               if (!v)
+                       tok_err(c, "error: name undeclared", &$2);
+               else if (!v->constant)
+                       tok_err(c, "error: array size must be a constant", &$2);
+
+               $0 = add_anon_type(c, &array_prototype, "array[%.*s]", $2.txt.len, $2.txt.txt);
+               $0->array.member = $<4;
+               $0->array.size = 0;
+               $0->array.vsize = v;
+       } }$
+
+###### formal type grammar
+
+       | [ ] Type ${ {
+               $0 = add_anon_type(c, &array_prototype, "array[]");
+               $0->array.member = $<Type;
+               $0->array.size = 0;
+               $0->array.unspec = 1;
+               $0->array.vsize = NULL;
+       } }$
+
+###### Binode types
+       Index, Length,
+
+###### term grammar
+
+       | Term [ Expression ] ${ {
+               struct binode *b = new(binode);
+               b->op = Index;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
+       } }$
+
+       | Term [ ] ${ {
+               struct binode *b = new(binode);
+               b->op = Length;
+               b->left = $<Term;
+               $0 = b;
+       } }$
+
+###### print binode cases
+       case Index:
+               print_exec(b->left, -1, bracket);
+               printf("[");
+               print_exec(b->right, -1, bracket);
+               printf("]");
+               break;
+
+       case Length:
+               print_exec(b->left, -1, bracket);
+               printf("[]");
+               break;
+
+###### propagate binode cases
+       case Index:
+               /* left must be an array, right must be a number,
+                * result is the member type of the array
+                */
+               propagate_types(b->right, c, perr_local, Tnum, 0);
+               t = propagate_types(b->left, c, perr, NULL, 0);
+               if (!t || t->compat != array_compat) {
+                       type_err(c, "error: %1 cannot be indexed", prog, t, 0,
+                                NULL);
+                       return NULL;
+               } else {
+                       if (!type_compat(type, t->array.member, rules)) {
+                               type_err(c, "error: have %1 but need %2", prog,
+                                        t->array.member, rules, type);
+                       }
+                       return t->array.member;
+               }
+               break;
+
+       case Length:
+               /* left must be an array, result is a number
+                */
+               t = propagate_types(b->left, c, perr, NULL, 0);
+               if (!t || t->compat != array_compat) {
+                       type_err(c, "error: %1 cannot provide length", prog, t,
+                                0, NULL);
+                       return NULL;
+               }
+               if (!type_compat(type, Tnum, rules))
+                       type_err(c, "error: have %1 but need %2", prog,
+                                        Tnum, rules, type);
+               return Tnum;
+               break;
+
+###### interp binode cases
+       case Index: {
+               mpz_t q;
+               long i;
+               void *ptr;
+
+               lleft = linterp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               mpz_init(q);
+               mpz_tdiv_q(q, mpq_numref(right.num), mpq_denref(right.num));
+               i = mpz_get_si(q);
+               mpz_clear(q);
+
+               if (ltype->array.static_size)
+                       ptr = lleft;
+               else
+                       ptr = *(void**)lleft;
+               rvtype = ltype->array.member;
+               if (i >= 0 && i < ltype->array.size)
+                       lrv = ptr + i * rvtype->size;
+               else
+                       val_init(ltype->array.member, &rv); // UNSAFE
+               ltype = NULL;
+               break;
+       }
+       case Length: {
+               lleft = linterp_exec(c, b->left, &ltype);
+               mpq_set_ui(rv.num, ltype->array.size, 1);
+               ltype = NULL;
+               rvtype = Tnum;
+               break;
+       }
+
+#### Structs
+
+A `struct` is a data-type that contains one or more other data-types.
+It differs from an array in that each member can be of a different
+type, and they are accessed by name rather than by number.  Thus you
+cannot choose an element by calculation, you need to know what you
+want up-front.
+
+The language makes no promises about how a given structure will be
+stored in memory - it is free to rearrange fields to suit whatever
+criteria seems important.
+
+Structs are declared separately from program code - they cannot be
+declared in-line in a variable declaration like arrays can.  A struct
+is given a name and this name is used to identify the type - the name
+is not prefixed by the word `struct` as it would be in C.
+
+Structs are only treated as the same if they have the same name.
+Simply having the same fields in the same order is not enough.  This
+might change once we can create structure initializers from a list of
+values.
+
+Each component datum is identified much like a variable is declared,
+with a name, one or two colons, and a type.  The type cannot be omitted
+as there is no opportunity to deduce the type from usage.  An initial
+value can be given following an equals sign, so
+
+##### Example: a struct type
+
+       struct complex:
+               x:number = 0
+               y:number = 0
+
+would declare a type called "complex" which has two number fields,
+each initialised to zero.
+
+Struct will need to be declared separately from the code that uses
+them, so we will need to be able to print out the declaration of a
+struct when reprinting the whole program.  So a `print_type_decl` type
+function will be needed.
+
+###### type union fields
+
+       struct {
+               int nfields;
+               struct field {
+                       struct text name;
+                       struct type *type;
+                       struct value *init;
+                       int offset;
+               } *fields; // This is created when field_list is analysed.
+               struct fieldlist {
+                       struct fieldlist *prev;
+                       struct field f;
+                       struct exec *init;
+               } *field_list; // This is created during parsing
+       } structure;
+
+###### type functions
+       void (*print_type_decl)(struct type *type, FILE *f);
+       struct type *(*fieldref)(struct type *t, struct parse_context *c,
+                                struct fieldref *f, struct value **vp);
+
+###### value functions
+
+       static void structure_init(struct type *type, struct value *val)
+       {
+               int i;
+
+               for (i = 0; i < type->structure.nfields; i++) {
+                       struct value *v;
+                       v = (void*) val->ptr + type->structure.fields[i].offset;
+                       if (type->structure.fields[i].init)
+                               dup_value(type->structure.fields[i].type,
+                                         type->structure.fields[i].init,
+                                         v);
+                       else
+                               val_init(type->structure.fields[i].type, v);
+               }
+       }
+
+       static void structure_free(struct type *type, struct value *val)
+       {
+               int i;
+
+               for (i = 0; i < type->structure.nfields; i++) {
+                       struct value *v;
+                       v = (void*)val->ptr + type->structure.fields[i].offset;
+                       free_value(type->structure.fields[i].type, v);
+               }
+       }
+
+       static void free_fieldlist(struct fieldlist *f)
+       {
+               if (!f)
+                       return;
+               free_fieldlist(f->prev);
+               free_exec(f->init);
+               free(f);
+       }
+
+       static void structure_free_type(struct type *t)
+       {
+               int i;
+               for (i = 0; i < t->structure.nfields; i++)
+                       if (t->structure.fields[i].init) {
+                               free_value(t->structure.fields[i].type,
+                                          t->structure.fields[i].init);
+                       }
+               free(t->structure.fields);
+               free_fieldlist(t->structure.field_list);
+       }
+
+       static int structure_prepare_type(struct parse_context *c,
+                                         struct type *t, int parse_time)
+       {
+               int cnt = 0;
+               struct fieldlist *f;
+
+               if (!parse_time || t->structure.fields)
+                       return 1;
+
+               for (f = t->structure.field_list; f; f=f->prev) {
+                       enum prop_err perr;
+                       cnt += 1;
+
+                       if (f->f.type->size <= 0)
+                               return 0;
+                       if (f->f.type->prepare_type)
+                               f->f.type->prepare_type(c, f->f.type, parse_time);
+
+                       if (f->init == NULL)
+                               continue;
+                       do {
+                               perr = 0;
+                               propagate_types(f->init, c, &perr, f->f.type, 0);
+                       } while (perr & Eretry);
+                       if (perr & Efail)
+                               c->parse_error += 1;    // NOTEST
+               }
+
+               t->structure.nfields = cnt;
+               t->structure.fields = calloc(cnt, sizeof(struct field));
+               f = t->structure.field_list;
+               while (cnt > 0) {
+                       int a = f->f.type->align;
+                       cnt -= 1;
+                       t->structure.fields[cnt] = f->f;
+                       if (t->size & (a-1))
+                               t->size = (t->size | (a-1)) + 1;
+                       t->structure.fields[cnt].offset = t->size;
+                       t->size += ((f->f.type->size - 1) | (a-1)) + 1;
+                       if (a > t->align)
+                               t->align = a;
+
+                       if (f->init && !c->parse_error) {
+                               struct value vl = interp_exec(c, f->init, NULL);
+                               t->structure.fields[cnt].init =
+                                       global_alloc(c, f->f.type, NULL, &vl);
+                       }
+
+                       f = f->prev;
+               }
+               return 1;
+       }
+
+       static int find_struct_index(struct type *type, struct text field)
+       {
+               int i;
+               for (i = 0; i < type->structure.nfields; i++)
+                       if (text_cmp(type->structure.fields[i].name, field) == 0)
+                               return i;
+               return IndexInvalid;
+       }
+
+       static struct type *structure_fieldref(struct type *t, struct parse_context *c,
+                                              struct fieldref *f, struct value **vp)
+       {
+               if (f->index == IndexUnknown) {
+                       f->index = find_struct_index(t, f->name);
+                       if (f->index < 0)
+                               type_err(c, "error: cannot find requested field in %1",
+                                        f->left, t, 0, NULL);
+               }
+               if (f->index < 0)
+                       return NULL;
+               if (vp) {
+                       struct value *v = *vp;
+                       v = (void*)v->ptr + t->structure.fields[f->index].offset;
+                       *vp = v;
+               }
+               return t->structure.fields[f->index].type;
+       }
+
+       static struct type structure_prototype = {
+               .init = structure_init,
+               .free = structure_free,
+               .free_type = structure_free_type,
+               .print_type_decl = structure_print_type,
+               .prepare_type = structure_prepare_type,
+               .fieldref = structure_fieldref,
+       };
+
+###### exec type
+       Xfieldref,
+
+###### ast
+       struct fieldref {
+               struct exec;
+               struct exec *left;
+               int index;
+               struct text name;
+       };
+       enum { IndexUnknown = -1, IndexInvalid = -2 };
+
+###### free exec cases
+       case Xfieldref:
+               free_exec(cast(fieldref, e)->left);
+               free(e);
+               break;
+
+###### declare terminals
+       $TERM struct
+
+###### term grammar
+
+       | Term . IDENTIFIER ${ {
+               struct fieldref *fr = new_pos(fieldref, $2);
+               fr->left = $<1;
+               fr->name = $3.txt;
+               fr->index = IndexUnknown;
+               $0 = fr;
+       } }$
+
+###### print exec cases
+
+       case Xfieldref:
+       {
+               struct fieldref *f = cast(fieldref, e);
+               print_exec(f->left, -1, bracket);
+               printf(".%.*s", f->name.len, f->name.txt);
+               break;
+       }
+
+###### propagate exec cases
+
+       case Xfieldref:
+       {
+               struct fieldref *f = cast(fieldref, prog);
+               struct type *st = propagate_types(f->left, c, perr, NULL, 0);
+
+               if (!st || !st->fieldref)
+                       type_err(c, "error: field reference on %1 is not supported",
+                                f->left, st, 0, NULL);
+               else {
+                       t = st->fieldref(st, c, f, NULL);
+                       if (t && !type_compat(type, t, rules))
+                               type_err(c, "error: have %1 but need %2", prog,
+                                        t, rules, type);
+                       return t;
+               }
+               break;
+       }
+
+###### interp exec cases
+       case Xfieldref:
+       {
+               struct fieldref *f = cast(fieldref, e);
+               struct type *ltype;
+               struct value *lleft = linterp_exec(c, f->left, &ltype);
+               lrv = lleft;
+               rvtype = ltype->fieldref(ltype, c, f, &lrv);
+               break;
+       }
+
+###### top level grammar
+       $*type
+       StructName -> IDENTIFIER ${ {
+               struct type *t = find_type(c, $ID.txt);
+
+               if (t && t->size >= 0) {
+                       tok_err(c, "error: type already declared", &$ID);
+                       tok_err(c, "info: this is location of declaration",
+                               &t->first_use);
+                       t = NULL;
+               }
+               if (!t)
+                       t = add_type(c, $ID.txt, NULL);
+               t->first_use = $ID;
+               $0 = t;
+       } }$
+       $void
+       DeclareStruct -> struct StructName FieldBlock Newlines ${ {
+               struct type *t = $<SN;
+               struct type tmp = *t;
+
+               *t = structure_prototype;
+               t->name = tmp.name;
+               t->next = tmp.next;
+               t->first_use = tmp.first_use;
+
+               t->structure.field_list = $<FB;
+       } }$
+
+       $*fieldlist
+       FieldBlock -> { IN OptNL FieldLines OUT OptNL } ${ $0 = $<FL; }$
+       | { SimpleFieldList } ${ $0 = $<SFL; }$
+       | IN OptNL FieldLines OUT ${ $0 = $<FL; }$
+       | SimpleFieldList EOL ${ $0 = $<SFL; }$
+
+       FieldLines -> SimpleFieldList Newlines ${ $0 = $<SFL; }$
+       | FieldLines SimpleFieldList Newlines ${ {
+               struct fieldlist *f = $<SFL;
+
+               if (f) {
+                       $0 = f;
+                       while (f->prev)
+                               f = f->prev;
+                       f->prev = $<FL;
+               } else
+                       $0 = $<FL;
+       } }$
+
+       SimpleFieldList -> Field ${ $0 = $<F; }$
+       | SimpleFieldList ; Field ${
+               $F->prev = $<SFL;
+               $0 = $<F;
+       }$
+       | SimpleFieldList ; ${
+               $0 = $<SFL;
+       }$
+       | ERROR ${ tok_err(c, "Syntax error in struct field", &$1); }$
+
+       Field -> IDENTIFIER : Type = Expression ${ {
+               $0 = calloc(1, sizeof(struct fieldlist));
+               $0->f.name = $ID.txt;
+               $0->f.type = $<Type;
+               $0->f.init = NULL;
+               $0->init = $<Expr;
+       } }$
+       | IDENTIFIER : Type ${
+               $0 = calloc(1, sizeof(struct fieldlist));
+               $0->f.name = $ID.txt;
+               $0->f.type = $<Type;
+       }$
+
+###### forward decls
+       static void structure_print_type(struct type *t, FILE *f);
 
-       static struct type *propagate_types(struct exec *prog, struct parse_context *c, int *ok,
-                                           struct type *type, int rules)
+###### value functions
+       static void structure_print_type(struct type *t, FILE *f)
        {
-               struct type *t;
+               int i;
+
+               fprintf(f, "struct %.*s\n", t->name.len, t->name.txt);
+
+               for (i = 0; i < t->structure.nfields; i++) {
+                       struct field *fl = t->structure.fields + i;
+                       fprintf(f, "    %.*s : ", fl->name.len, fl->name.txt);
+                       type_print(fl->type, f);
+                       if (fl->type->print && fl->init) {
+                               fprintf(f, " = ");
+                               if (fl->type == Tstr)
+                                       fprintf(f, "\"");
+                               print_value(fl->type, fl->init, f);
+                               if (fl->type == Tstr)
+                                       fprintf(f, "\"");
+                       }
+                       fprintf(f, "\n");
+               }
+       }
 
-               if (!prog)
-                       return Tnone;
+###### print type decls
+       {
+               struct type *t;
+               int target = -1;
+
+               while (target != 0) {
+                       int i = 0;
+                       for (t = context.typelist; t ; t=t->next)
+                               if (!t->anon && t->print_type_decl &&
+                                   !t->check_args) {
+                                       i += 1;
+                                       if (i == target)
+                                               break;
+                               }
 
-               switch (prog->type) {
-               case Xbinode:
-               {
-                       struct binode *b = cast(binode, prog);
-                       switch (b->op) {
-                       ## propagate binode cases
+                       if (target == -1) {
+                               target = i;
+                       } else {
+                               t->print_type_decl(t, stdout);
+                               target -= 1;
                        }
-                       break;
-               }
-               ## propagate exec cases
                }
-               return Tnone;
        }
 
-#### Interpreting
+#### References
+
+References, or pointers, are values that refer to another value.  They
+can only refer to a type that is named, which excludes arrays or other
+references.  As these can be included in a struct which is named, it is
+still possible to reference an array or reference - though indirectly.
+
+References are potentially dangerous as they might refer to some
+variable which no longer exists - either because a stack frame
+containing it has been discarded or because the value was allocated on
+the heap and has now been free.  Ocean does not yet provide any
+protection against these problems.  It will in due course.
+
+With references comes the opportunity and the need to explicitly
+allocate values on the "heap" and to free them.  We currently provide
+fairly basic support for this.
+
+Reference make use of the `@` symbol in various ways.  A type that starts
+with `@` is a reference to whatever follows.  A reference value
+followed by an `@` acts as the referred value, though the `@` is often
+not needed.  Finally, an expression that starts with `@` is a special
+reference related expression.  Some examples might help.
+
+##### Example: Reference examples
+
+       struct foo
+               a: number
+               b: string
+       ref: @foo
+       bar: foo
+       bar.number = 23; bar.string = "hello"
+       baz: foo
+       ref = bar
+       baz = @ref
+       baz.a = ref.a * 2
+
+       ref = @new()
+       ref@ = baz
+       @free = ref
+       ref = @nil
+
+Obviously this is very contrived.  `ref` is a reference to a `foo` which
+is initially set to refer to the value stored in `bar` - no extra syntax
+is needed to "Take the address of" `bar` - the fact that `ref` is a
+reference means that only the address make sense.
+
+When `ref.a` is accessed, that is whatever value is stored in `bar.a`.
+The same syntax is used for accessing fields both in structs and in
+references to structs.  It would be correct to use `ref@.a`, but not
+necessary.
+
+`@new()` creates an object of whatever type is needed for the program to
+by type-correct.  In future iterations of Ocean, a constructor will
+access arguments, so the the syntax now looks like a function call.
+`@free` can be assigned any reference that was returned by `@new()`, and
+it will be freed.  `@nil` is a value of whatever reference type is
+appropriate, and is stable and never the address of anything in the heap
+or on the stack.  A reference can be assigned `nil` or compared against
+that value.
+
+###### declare terminals
+       $TERM @
+
+###### type union fields
 
-Interpreting an `exec` doesn't require anything but the `exec`.  State
-is stored in variables and each variable will be directly linked from
-within the `exec` tree.  The exception to this is the whole `program`
-which needs to look at command line arguments.  The `program` will be
-interpreted separately.
+       struct {
+               struct type *referent;
+       } reference;
 
-Each `exec` can return a value, which may be `Tnone` but must be non-NULL;
+###### value union fields
+       struct value *ref;
 
-###### core functions
+###### value functions
 
-       static struct value interp_exec(struct exec *e)
+       static void reference_print_type(struct type *t, FILE *f)
        {
-               struct value rv;
-               rv.type = Tnone;
-               if (!e)
-                       return rv;
+               fprintf(f, "@");
+               type_print(t->reference.referent, f);
+       }
 
-               switch(e->type) {
-               case Xbinode:
-               {
-                       struct binode *b = cast(binode, e);
-                       struct value left, right;
-                       left.type = right.type = Tnone;
-                       switch (b->op) {
-                       ## interp binode cases
-                       }
-                       free_value(left); free_value(right);
-                       break;
-               }
-               ## interp exec cases
+       static int reference_cmp(struct type *tl, struct type *tr,
+                                struct value *left, struct value *right)
+       {
+               return left->ref == right->ref ? 0 : 1;
+       }
+
+       static void reference_dup(struct type *t,
+                                 struct value *vold, struct value *vnew)
+       {
+               vnew->ref = vold->ref;
+       }
+
+       static void reference_free(struct type *t, struct value *v)
+       {
+               /* Nothing to do here */
+       }
+
+       static int reference_compat(struct type *require, struct type *have,
+                                   enum val_rules rules)
+       {
+               if (rules & Rrefok)
+                       if (require->reference.referent == have)
+                               return 1;
+               if (have->compat != require->compat)
+                       return 0;
+               if (have->reference.referent != require->reference.referent)
+                       return 0;
+               return 1;
+       }
+
+       static int reference_test(struct type *type, struct value *val)
+       {
+               return val->ref != NULL;
+       }
+
+       static struct type *reference_fieldref(
+               struct type *t, struct parse_context *c, struct fieldref *f,
+               struct value **vp)
+       {
+               struct type *rt = t->reference.referent;
+
+               if (rt->fieldref) {
+                       if (vp)
+                               *vp = (*vp)->ref;
+                       return rt->fieldref(rt, c, f, vp);
                }
-               return rv;
+               type_err(c, "error: field reference on %1 is not supported",
+                                f->left, rt, 0, NULL);
+               return Tnone;
        }
 
-## Language elements
+       static struct type reference_prototype = {
+               .print_type = reference_print_type,
+               .cmp_eq = reference_cmp,
+               .dup = reference_dup,
+               .test = reference_test,
+               .free = reference_free,
+               .compat = reference_compat,
+               .fieldref = reference_fieldref,
+               .size = sizeof(void*),
+               .align = sizeof(void*),
+       };
 
-Each language element needs to be parsed, printed, analysed,
-interpreted, and freed.  There are several, so let's just start with
-the easy ones and work our way up.
+###### type grammar
 
-### Values
+       | @ IDENTIFIER ${ {
+               struct type *t = find_type(c, $ID.txt);
+               if (!t) {
+                       t = add_type(c, $ID.txt, NULL);
+                       t->first_use = $ID;
+               }
+               $0 = find_anon_type(c, &reference_prototype, "@%.*s",
+                                   $ID.txt.len, $ID.txt.txt);
+               $0->reference.referent = t;
+       } }$
 
-We have already met values as separate objects.  When manifest
-constants appear in the program text, that must result in an executable
-which has a constant value.  So the `val` structure embeds a value in
-an executable.
+###### core functions
+       static int text_is(struct text t, char *s)
+       {
+               return (strlen(s) == t.len &&
+                       strncmp(s, t.txt, t.len) == 0);
+       }
 
 ###### exec type
-       Xval,
+       Xref,
 
 ###### ast
-       struct val {
+       struct ref {
                struct exec;
-               struct value val;
+               enum ref_func { RefNew, RefFree, RefNil } action;
+               struct type *reftype;
+               struct exec *right;
        };
 
-###### Grammar
+###### SimpleStatement Grammar
 
-       $*val
-       Value ->  True ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tbool;
-                       $0->val.bool = 1;
-                       }$
-               | False ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tbool;
-                       $0->val.bool = 0;
-                       }$
-               | NUMBER ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tnum;
-                       {
-                       char tail[3];
-                       if (number_parse($0->val.num, tail, $1.txt) == 0)
-                               mpq_init($0->val.num);
-                               if (tail[0])
-                                       tok_err(config2context(config), "error: unsupported number suffix.",
-                                               &$1);
-                       }
-                       }$
-               | STRING ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tstr;
-                       {
-                       char tail[3];
-                       string_parse(&$1, '\\', &$0->val.str, tail);
-                       if (tail[0])
-                               tok_err(config2context(config), "error: unsupported string suffix.",
-                                       &$1);
-                       }
-                       }$
-               | MULTI_STRING ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tstr;
-                       {
-                       char tail[3];
-                       string_parse(&$1, '\\', &$0->val.str, tail);
-                       if (tail[0])
-                               tok_err(config2context(config), "error: unsupported string suffix.",
-                                       &$1);
-                       }
-                       }$
+       | @ IDENTIFIER = Expression ${ {
+               struct ref *r = new_pos(ref, $ID);
+               // Must be "free"
+               if (!text_is($ID.txt, "free"))
+                       tok_err(c, "error: only \"@free\" makes sense here",
+                               &$ID);
+
+               $0 = r;
+               r->action = RefFree;
+               r->right = $<Exp;
+       } }$
+
+###### expression grammar
+       | @ IDENTIFIER ( ) ${
+               // Only 'new' valid here
+               if (!text_is($ID.txt, "new")) {
+                       tok_err(c, "error: Only reference function is \"@new()\"",
+                               &$ID);
+               } else {
+                       struct ref *r = new_pos(ref,$ID);
+                       $0 = r;
+                       r->action = RefNew;
+               }
+       }$
+       | @ IDENTIFIER ${
+               // Only 'nil' valid here
+               if (!text_is($ID.txt, "nil")) {
+                       tok_err(c, "error: Only reference value is \"@nil\"",
+                               &$ID);
+               } else {
+                       struct ref *r = new_pos(ref,$ID);
+                       $0 = r;
+                       r->action = RefNil;
+               }
+       }$
 
 ###### print exec cases
-       case Xval:
-       {
-               struct val *v = cast(val, e);
-               if (v->val.type == Tstr)
-                       printf("\"");
-               print_value(v->val);
-               if (v->val.type == Tstr)
-                       printf("\"");
+       case Xref: {
+               struct ref *r = cast(ref, e);
+               switch (r->action) {
+               case RefNew:
+                       printf("@new()"); break;
+               case RefNil:
+                       printf("@nil"); break;
+               case RefFree:
+                       do_indent(indent, "@free = ");
+                       print_exec(r->right, indent, bracket);
+                       break;
+               }
                break;
        }
 
 ###### propagate exec cases
-               case Xval:
-               {
-                       struct val *val = cast(val, prog);
-                       if (!vtype_compat(type, val->val.type, rules)) {
-                               type_err(c, "error: expected %1%r found %2",
-                                          prog, type, rules, val->val.type);
-                               *ok = 0;
+       case Xref: {
+               struct ref *r = cast(ref, prog);
+               switch (r->action) {
+               case RefNew:
+                       if (type && type->free != reference_free) {
+                               type_err(c, "error: @new() can only be used with references, not %1",
+                                        prog, type, 0, NULL);
+                               return NULL;
                        }
-                       return val->val.type;
+                       if (type && !r->reftype) {
+                               r->reftype = type;
+                               *perr |= Eretry;
+                       }
+                       *perr |= Erval;
+                       return type;
+               case RefNil:
+                       if (type && type->free != reference_free)
+                               type_err(c, "error: @nil can only be used with reference, not %1",
+                                        prog, type, 0, NULL);
+                       if (type && !r->reftype) {
+                               r->reftype = type;
+                               *perr |= Eretry;
+                       }
+                       *perr |= Erval;
+                       return type;
+               case RefFree:
+                       t = propagate_types(r->right, c, perr_local, NULL, 0);
+                       if (t && t->free != reference_free)
+                               type_err(c, "error: @free can only be assigned a reference, not %1",
+                                        prog, t, 0, NULL);
+                       r->reftype = Tnone;
+                       return Tnone;
                }
+               break;  // NOTEST
+       }
 
 ###### interp exec cases
-       case Xval:
-               return dup_value(cast(val, e)->val);
+       case Xref: {
+               struct ref *r = cast(ref, e);
+               switch (r->action) {
+               case RefNew:
+                       if (r->reftype)
+                               rv.ref = calloc(1, r->reftype->reference.referent->size);
+                       rvtype = r->reftype;
+                       break;
+               case RefNil:
+                       rv.ref = NULL;
+                       rvtype = r->reftype;
+                       break;
+               case RefFree:
+                       rv = interp_exec(c, r->right, &rvtype);
+                       free_value(rvtype->reference.referent, rv.ref);
+                       free(rv.ref);
+                       rvtype = Tnone;
+                       break;
+               }
+               break;
+       }
 
-###### ast functions
-       static void free_val(struct val *v)
+###### free exec cases
+       case Xref: {
+               struct ref *r = cast(ref, e);
+               free_exec(r->right);
+               free(r);
+               break;
+       }
+
+###### Expressions: dereference
+
+###### Binode types
+       Deref, AddressOf,
+
+###### term grammar
+
+       | Term @ ${ {
+               struct binode *b = new(binode);
+               b->op = Deref;
+               b->left = $<Trm;
+               $0 = b;
+       } }$
+
+###### print binode cases
+       case Deref:
+               print_exec(b->left, -1, bracket);
+               printf("@");
+               break;
+       case AddressOf:
+               print_exec(b->left, -1, bracket);
+               break;
+
+###### propagate binode cases
+       case Deref:
+               /* left must be a reference, and we return what it refers to */
+               /* FIXME how can I pass the expected type down? */
+               t = propagate_types(b->left, c, perr, NULL, 0);
+               *perr &= ~Erval;
+               if (!t || t->free != reference_free)
+                       type_err(c, "error: Cannot dereference %1", b, t, 0, NULL);
+               else
+                       return t->reference.referent;
+               break;
+
+       case AddressOf:
+               /* left must be lval, we create reference to it */
+               if (!type || type->free != reference_free)
+                       t = propagate_types(b->left, c, perr, type, 0); // NOTEST impossible
+               else
+                       t = propagate_types(b->left, c, perr,
+                                           type->reference.referent, 0);
+               if (t)
+                       t = find_anon_type(c, &reference_prototype, "@%.*s",
+                                       t->name.len, t->name.txt);
+               return t;
+
+###### interp binode cases
+       case Deref:
+               left = interp_exec(c, b->left, &ltype);
+               lrv = left.ref;
+               rvtype = ltype->reference.referent;
+               break;
+
+       case AddressOf:
+               rv.ref = linterp_exec(c, b->left, &rvtype);
+               rvtype = find_anon_type(c, &reference_prototype, "@%.*s",
+                                       rvtype->name.len, rvtype->name.txt);
+               break;
+
+#### Functions
+
+A function is a chunk of code which can be passed parameters and can
+return results.  Each function has a type which includes the set of
+parameters and the return value.  As yet these types cannot be declared
+separately from the function itself.
+
+The parameters can be specified either in parentheses as a ';' separated
+list, such as
+
+##### Example: function 1
+
+       func main(av:[]string; env:[]string)
+               code block
+
+or as an indented list of one parameter per line (though each line can
+be a ';' separated list)
+
+##### Example: function 2
+
+       func main
+               argv:[]string
+               env:[]string
+       do
+               code block
+
+In the first case a return type can follow the parentheses after a colon,
+in the second it is given on a line starting with the word `return`.
+
+##### Example: functions that return
+
+       func add(a:number; b:number): number
+               code block
+
+       func catenate
+               a: string
+               b: string
+       return string
+       do
+               code block
+
+Rather than returning a type, the function can specify a set of local
+variables to return as a struct.  The values of these variables when the
+function exits will be provided to the caller.  For this the return type
+is replaced with a block of result declarations, either in parentheses
+or bracketed by `return` and `do`.
+
+##### Example: functions returning multiple variables
+
+       func to_cartesian(rho:number; theta:number):(x:number; y:number)
+               x = .....
+               y = .....
+
+       func to_polar
+               x:number; y:number
+       return
+               rho:number
+               theta:number
+       do
+               rho = ....
+               theta = ....
+
+For constructing the lists we use a `List` binode, which will be
+further detailed when Expression Lists are introduced.
+
+###### type union fields
+
+       struct {
+               struct binode *params;
+               struct type *return_type;
+               struct variable *scope;
+               int inline_result;      // return value is at start of 'local'
+               int local_size;
+       } function;
+
+###### value union fields
+       struct exec *function;
+
+###### type functions
+       void (*check_args)(struct parse_context *c, enum prop_err *perr,
+                          struct type *require, struct exec *args);
+
+###### value functions
+
+       static void function_free(struct type *type, struct value *val)
        {
-               if (!v)
-                       return;
-               free_value(v->val);
-               free(v);
+               free_exec(val->function);
+               val->function = NULL;
        }
 
-###### free exec cases
-       case Xval: free_val(cast(val, e)); break;
+       static int function_compat(struct type *require, struct type *have,
+                                  enum val_rules rules)
+       {
+               // FIXME can I do anything here yet?
+               return 0;
+       }
 
-###### ast functions
-       // Move all nodes from 'b' to 'rv', reversing the order.
-       // In 'b' 'left' is a list, and 'right' is the last node.
-       // In 'rv', left' is the first node and 'right' is a list.
-       static struct binode *reorder_bilist(struct binode *b)
+       static struct exec *take_addr(struct exec *e)
        {
-               struct binode *rv = NULL;
+               struct binode *rv = new(binode);
+               rv->op = AddressOf;
+               rv->left = e;
+               return rv;
+       }
 
-               while (b) {
-                       struct exec *t = b->right;
-                       b->right = rv;
-                       rv = b;
-                       if (b->left)
-                               b = cast(binode, b->left);
-                       else
-                               b = NULL;
-                       rv->left = t;
+       static void function_check_args(struct parse_context *c, enum prop_err *perr,
+                                       struct type *require, struct exec *args)
+       {
+               /* This should be 'compat', but we don't have a 'tuple' type to
+                * hold the type of 'args'
+                */
+               struct binode *arg = cast(binode, args);
+               struct binode *param = require->function.params;
+
+               while (param) {
+                       struct var *pv = cast(var, param->left);
+                       struct type *t = pv->var->type, *t2;
+                       if (!arg) {
+                               type_err(c, "error: insufficient arguments to function.",
+                                        args, NULL, 0, NULL);
+                               break;
+                       }
+                       *perr = 0;
+                       t2 = propagate_types(arg->left, c, perr, t, Rrefok);
+                       if (t->free == reference_free &&
+                           t->reference.referent == t2 &&
+                           !(*perr & Erval)) {
+                               arg->left = take_addr(arg->left);
+                       } else if (!(*perr & Efail) && !type_compat(t2, t, 0)) {
+                               type_err(c, "error: cannot pass rval when reference expected",
+                                        arg->left, NULL, 0, NULL);
+                       }
+                       param = cast(binode, param->right);
+                       arg = cast(binode, arg->right);
                }
-               return rv;
+               if (arg)
+                       type_err(c, "error: too many arguments to function.",
+                                args, NULL, 0, NULL);
        }
 
-### Variables
+       static void function_print(struct type *type, struct value *val, FILE *f)
+       {
+               fprintf(f, "\n");
+               print_exec(val->function, 1, 0);
+       }
 
-Just as we used a `val` to wrap a value into an `exec`, we similarly
-need a `var` to wrap a `variable` into an exec.  While each `val`
-contained a copy of the value, each `var` hold a link to the variable
-because it really is the same variable no matter where it appears.
-When a variable is used, we need to remember to follow the `->merged`
-link to find the primary instance.
+       static void function_print_type_decl(struct type *type, FILE *f)
+       {
+               struct binode *b;
+               fprintf(f, "(");
+               for (b = type->function.params; b; b = cast(binode, b->right)) {
+                       struct variable *v = cast(var, b->left)->var;
+                       fprintf(f, "%.*s%s", v->name->name.len, v->name->name.txt,
+                               v->constant ? "::" : ":");
+                       type_print(v->type, f);
+                       if (b->right)
+                               fprintf(f, "; ");
+               }
+               fprintf(f, ")");
+               if (type->function.return_type != Tnone) {
+                       fprintf(f, ":");
+                       if (type->function.inline_result) {
+                               int i;
+                               struct type *t = type->function.return_type;
+                               fprintf(f, " (");
+                               for (i = 0; i < t->structure.nfields; i++) {
+                                       struct field *fl = t->structure.fields + i;
+                                       if (i)
+                                               fprintf(f, "; ");
+                                       fprintf(f, "%.*s:", fl->name.len, fl->name.txt);
+                                       type_print(fl->type, f);
+                               }
+                               fprintf(f, ")");
+                       } else
+                               type_print(type->function.return_type, f);
+               }
+       }
 
-###### exec type
-       Xvar,
+       static void function_free_type(struct type *t)
+       {
+               free_exec(t->function.params);
+       }
 
-###### ast
-       struct var {
-               struct exec;
-               struct variable *var;
+       static struct type function_prototype = {
+               .size = sizeof(void*),
+               .align = sizeof(void*),
+               .free = function_free,
+               .compat = function_compat,
+               .check_args = function_check_args,
+               .print = function_print,
+               .print_type_decl = function_print_type_decl,
+               .free_type = function_free_type,
        };
 
+###### declare terminals
+
+       $TERM func
+
 ###### Grammar
 
-       $*var
-       VariableDecl -> IDENTIFIER := ${ {
-               struct variable *v = var_decl(config2context(config), $1.txt);
-               $0 = new_pos(var, $1);
-               $0->var = v;
-               if (v)
-                       v->where_decl = $0;
-               else {
-                       v = var_ref(config2context(config), $1.txt);
-                       $0->var = v;
-                       type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
-                       type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
-               }
-       } }$
-           | IDENTIFIER ::= ${ {
-               struct variable *v = var_decl(config2context(config), $1.txt);
-               $0 = new_pos(var, $1);
-               $0->var = v;
+       $*variable
+       FuncName -> IDENTIFIER ${ {
+               struct variable *v = var_decl(c, $1.txt);
+               struct var *e = new_pos(var, $1);
+               e->var = v;
                if (v) {
-                       v->where_decl = $0;
-                       v->constant = 1;
+                       v->where_decl = e;
+                       v->where_set = e;
+                       $0 = v;
                } else {
-                       v = var_ref(config2context(config), $1.txt);
-                       $0->var = v;
-                       type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
-                       type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
+                       v = var_ref(c, $1.txt);
+                       e->var = v;
+                       type_err(c, "error: function '%v' redeclared",
+                               e, NULL, 0, NULL);
+                       type_err(c, "info: this is where '%v' was first declared",
+                               v->where_decl, NULL, 0, NULL);
+                       free_exec(e);
                }
        } }$
-           | IDENTIFIER : Type = ${ {
-               struct variable *v = var_decl(config2context(config), $1.txt);
-               $0 = new_pos(var, $1);
+
+       $*binode
+       Args -> ArgsLine NEWLINE ${ $0 = $<AL; }$
+       | Args ArgsLine NEWLINE ${ {
+               struct binode *b = $<AL;
+               struct binode **bp = &b;
+               while (*bp)
+                       bp = (struct binode **)&(*bp)->left;
+               *bp = $<A;
+               $0 = b;
+       } }$
+
+       ArgsLine -> ${ $0 = NULL; }$
+       | Varlist ${ $0 = $<1; }$
+       | Varlist ; ${ $0 = $<1; }$
+
+       Varlist -> Varlist ; ArgDecl ${
+               $0 = new_pos(binode, $2);
+               $0->op = List;
+               $0->left = $<Vl;
+               $0->right = $<AD;
+       }$
+       | ArgDecl ${
+               $0 = new(binode);
+               $0->op = List;
+               $0->left = NULL;
+               $0->right = $<AD;
+       }$
+
+       $*var
+       ArgDecl -> IDENTIFIER : FormalType ${ {
+               struct variable *v = var_decl(c, $ID.txt);
+               $0 = new_pos(var, $ID);
                $0->var = v;
-               if (v) {
-                       v->where_decl = $0;
-                       v->where_set = $0;
-                       v->val = val_init($<3);
-               } else {
-                       v = var_ref(config2context(config), $1.txt);
-                       $0->var = v;
-                       type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
-                       type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
-               }
+               v->where_decl = $0;
+               v->where_set = $0;
+               v->type = $<FT;
+       } }$
+
+##### Function calls
+
+A function call can appear either as an expression or as a statement.
+We use a new 'Funcall' binode type to link the function with a list of
+arguments, form with the 'List' nodes.
+
+We have already seen the "Term" which is how a function call can appear
+in an expression.  To parse a function call into a statement we include
+it in the "SimpleStatement Grammar" which will be described later.
+
+###### Binode types
+       Funcall,
+
+###### term grammar
+       | Term ( ExpressionList ) ${ {
+               struct binode *b = new(binode);
+               b->op = Funcall;
+               b->left = $<T;
+               b->right = reorder_bilist($<EL);
+               $0 = b;
+       } }$
+       | Term ( ) ${ {
+               struct binode *b = new(binode);
+               b->op = Funcall;
+               b->left = $<T;
+               b->right = NULL;
+               $0 = b;
        } }$
-           | IDENTIFIER :: Type = ${ {
-               struct variable *v = var_decl(config2context(config), $1.txt);
-               $0 = new_pos(var, $1);
-               $0->var = v;
-               if (v) {
-                       v->where_decl = $0;
-                       v->where_set = $0;
-                       v->val = val_init($<3);
-                       v->constant = 1;
-               } else {
-                       v = var_ref(config2context(config), $1.txt);
-                       $0->var = v;
-                       type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
-                       type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
-               }
+
+###### SimpleStatement Grammar
+
+       | Term ( ExpressionList ) ${ {
+               struct binode *b = new(binode);
+               b->op = Funcall;
+               b->left = $<T;
+               b->right = reorder_bilist($<EL);
+               $0 = b;
        } }$
 
-       Variable -> IDENTIFIER ${ {
-               struct variable *v = var_ref(config2context(config), $1.txt);
-               $0 = new_pos(var, $1);
-               if (v == NULL) {
-                       /* This might be a label - allocate a var just in case */
-                       v = var_decl(config2context(config), $1.txt);
-                       if (v) {
-                               v->val = val_init(Tlabel);
-                               v->val.label = &v->val;
-                               v->where_set = $0;
+###### print binode cases
+
+       case Funcall:
+               do_indent(indent, "");
+               print_exec(b->left, -1, bracket);
+               printf("(");
+               for (b = cast(binode, b->right); b; b = cast(binode, b->right)) {
+                       if (b->left) {
+                               printf(" ");
+                               print_exec(b->left, -1, bracket);
+                               if (b->right)
+                                       printf(",");
                        }
                }
-               $0->var = v;
-       } }$
+               printf(")");
+               if (indent >= 0)
+                       printf("\n");
+               break;
 
-       $*type
-       Type -> IDENTIFIER ${
-               $0 = find_type(config2context(config), $1.txt);
-               if (!$0) {
-                       tok_err(config2context(config),
-                               "error: undefined type", &$1);
+###### propagate binode cases
 
-                       $0 = Tnone;
-               }
-       }$
+       case Funcall: {
+               /* Every arg must match formal parameter, and result
+                * is return type of function
+                */
+               struct binode *args = cast(binode, b->right);
+               struct var *v = cast(var, b->left);
 
-###### print exec cases
-       case Xvar:
-       {
-               struct var *v = cast(var, e);
-               if (v->var) {
-                       struct binding *b = v->var->name;
-                       printf("%.*s", b->name.len, b->name.txt);
+               if (!v->var->type || v->var->type->check_args == NULL) {
+                       type_err(c, "error: attempt to call a non-function.",
+                                prog, NULL, 0, NULL);
+                       return NULL;
                }
-               break;
+               *perr |= Eruntime;
+               v->var->type->check_args(c, perr_local, v->var->type, args);
+               if (v->var->type->function.inline_result)
+                       *perr |= Emaycopy;
+               *perr |= Erval;
+               return v->var->type->function.return_type;
        }
 
-###### format cases
-       case 'v':
-               if (loc->type == Xvar) {
-                       struct var *v = cast(var, loc);
-                       if (v->var) {
-                               struct binding *b = v->var->name;
-                               fprintf(stderr, "%.*s", b->name.len, b->name.txt);
-                       } else
-                               fputs("???", stderr);
+###### interp binode cases
+
+       case Funcall: {
+               struct var *v = cast(var, b->left);
+               struct type *t = v->var->type;
+               void *oldlocal = c->local;
+               int old_size = c->local_size;
+               void *local = calloc(1, t->function.local_size);
+               struct value *fbody = var_value(c, v->var);
+               struct binode *arg = cast(binode, b->right);
+               struct binode *param = t->function.params;
+
+               while (param) {
+                       struct var *pv = cast(var, param->left);
+                       struct type *vtype = NULL;
+                       struct value val = interp_exec(c, arg->left, &vtype);
+                       struct value *lval;
+                       c->local = local; c->local_size = t->function.local_size;
+                       lval = var_value(c, pv->var);
+                       c->local = oldlocal; c->local_size = old_size;
+                       memcpy(lval, &val, vtype->size);
+                       param = cast(binode, param->right);
+                       arg = cast(binode, arg->right);
+               }
+               c->local = local; c->local_size = t->function.local_size;
+               if (t->function.inline_result && dtype) {
+                       _interp_exec(c, fbody->function, NULL, NULL);
+                       memcpy(dest, local, dtype->size);
+                       rvtype = ret.type = NULL;
                } else
-                       fputs("NOTVAR", stderr);
+                       rv = interp_exec(c, fbody->function, &rvtype);
+               c->local = oldlocal; c->local_size = old_size;
+               free(local);
                break;
+       }
 
-###### propagate exec cases
+## Complex executables: statements and expressions
 
-       case Xvar:
-       {
-               struct var *var = cast(var, prog);
-               struct variable *v = var->var;
-               if (!v) {
-                       type_err(c, "%d:BUG: no variable!!", prog, Tnone, 0, Tnone);
-                       *ok = 0;
-                       return Tnone;
-               }
-               if (v->merged)
-                       v = v->merged;
-               if (v->val.type == NULL) {
-                       if (type && *ok != 0) {
-                               v->val = val_init(type);
-                               v->where_set = prog;
-                               *ok = 2;
-                       }
-                       return type;
-               }
-               if (!vtype_compat(type, v->val.type, rules)) {
-                       type_err(c, "error: expected %1%r but variable '%v' is %2", prog,
-                                type, rules, v->val.type);
-                       type_err(c, "info: this is where '%v' was set to %1", v->where_set,
-                                v->val.type, rules, Tnone);
-                       *ok = 0;
-               }
-               if (!type)
-                       return v->val.type;
-               return type;
-       }
+Now that we have types, values, variables, and most of the basic
+Terms which provide access to these, we can explore the more complex
+code that combine all of these to get useful work done.  Specifically
+statements and expressions.
 
-###### interp exec cases
-       case Xvar:
-       {
-               struct var *var = cast(var, e);
-               struct variable *v = var->var;
+Expressions are various combinations of Terms.  We will use operator
+precedence to ensure correct parsing.  The simplest Expression is just a
+Term - others will follow.
 
-               if (v->merged)
-                       v = v->merged;
-               return dup_value(v->val);
-       }
+###### Grammar
 
-###### ast functions
+       $*exec
+       Expression -> Term ${ $0 = $<Term; }$
+       ## expression grammar
 
-       static void free_var(struct var *v)
-       {
-               free(v);
+### Expressions: Conditional
+
+Our first user of the `binode` will be conditional expressions, which
+is a bit odd as they actually have three components.  That will be
+handled by having 2 binodes for each expression.  The conditional
+expression is the lowest precedence operator which is why we define it
+first - to start the precedence list.
+
+Conditional expressions are of the form "value `if` condition `else`
+other_value".  They associate to the right, so everything to the right
+of `else` is part of an else value, while only a higher-precedence to
+the left of `if` is the if value.  Between `if` and `else` there is no
+room for ambiguity, so a full conditional expression is allowed in
+there.
+
+###### Binode types
+       CondExpr,
+
+###### declare terminals
+
+       $LEFT if $$ifelse
+
+###### expression grammar
+
+       | Expression if Expression else Expression $$ifelse ${ {
+               struct binode *b1 = new(binode);
+               struct binode *b2 = new(binode);
+               b1->op = CondExpr;
+               b1->left = $<3;
+               b1->right = b2;
+               b2->op = CondExpr;
+               b2->left = $<1;
+               b2->right = $<5;
+               $0 = b1;
+       } }$
+
+###### print binode cases
+
+       case CondExpr:
+               b2 = cast(binode, b->right);
+               if (bracket) printf("(");
+               print_exec(b2->left, -1, bracket);
+               printf(" if ");
+               print_exec(b->left, -1, bracket);
+               printf(" else ");
+               print_exec(b2->right, -1, bracket);
+               if (bracket) printf(")");
+               break;
+
+###### propagate binode cases
+
+       case CondExpr: {
+               /* cond must be Tbool, others must match */
+               struct binode *b2 = cast(binode, b->right);
+               struct type *t2;
+
+               propagate_types(b->left, c, perr_local, Tbool, 0);
+               t = propagate_types(b2->left, c, perr, type, 0);
+               t2 = propagate_types(b2->right, c, perr, type ?: t, 0);
+               return t ?: t2;
        }
 
-###### free exec cases
-       case Xvar: free_var(cast(var, e)); break;
+###### interp binode cases
+
+       case CondExpr: {
+               struct binode *b2 = cast(binode, b->right);
+               left = interp_exec(c, b->left, &ltype);
+               if (left.bool)
+                       rv = interp_exec(c, b2->left, &rvtype);
+               else
+                       rv = interp_exec(c, b2->right, &rvtype);
+               }
+               break;
 
 ### Expressions: Boolean
 
-Our first user of the `binode` will be expressions, and particularly
-Boolean expressions.  As I haven't implemented precedence in the
-parser generator yet, we need different names from each precedence
-level used by expressions.  The outer most or lowest level precedence
-are Boolean `or` `and`, and `not` which form an `Expression` out of `BTerm`s
-and `BFact`s.
+The next class of expressions to use the `binode` will be Boolean
+expressions.  `and` and `or` are short-circuit operators that don't
+evaluate the second expression if not necessary.
 
 ###### Binode types
        And,
        Or,
        Not,
 
-###### Grammar
-
-       $*exec
-       Expression -> Expression or BTerm ${ {
-                       struct binode *b = new(binode);
-                       b->op = Or;
-                       b->left = $<1;
-                       b->right = $<3;
-                       $0 = b;
-               } }$
-               | BTerm ${ $0 = $<1; }$
-
-       BTerm -> BTerm and BFact ${ {
-                       struct binode *b = new(binode);
-                       b->op = And;
-                       b->left = $<1;
-                       b->right = $<3;
-                       $0 = b;
-               } }$
-               | BFact ${ $0 = $<1; }$
-
-       BFact -> not BFact ${ {
-                       struct binode *b = new(binode);
-                       b->op = Not;
-                       b->right = $<2;
-                       $0 = b;
-               } }$
-               ## other BFact
+###### declare terminals
+       $LEFT or
+       $LEFT and
+       $LEFT not
+
+###### expression grammar
+       | Expression or Expression ${ {
+               struct binode *b = new(binode);
+               b->op = Or;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
+       } }$
+       | Expression and Expression ${ {
+               struct binode *b = new(binode);
+               b->op = And;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
+       } }$
+       | not Expression ${ {
+               struct binode *b = new(binode);
+               b->op = Not;
+               b->right = $<2;
+               $0 = b;
+       } }$
 
 ###### print binode cases
        case And:
-               print_exec(b->left, -1, 0);
+               if (bracket) printf("(");
+               print_exec(b->left, -1, bracket);
                printf(" and ");
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
                break;
        case Or:
-               print_exec(b->left, -1, 0);
+               if (bracket) printf("(");
+               print_exec(b->left, -1, bracket);
                printf(" or ");
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
                break;
        case Not:
+               if (bracket) printf("(");
                printf("not ");
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
                break;
 
 ###### propagate binode cases
@@ -1828,40 +4064,38 @@ and `BFact`s.
        case Or:
        case Not:
                /* both must be Tbool, result is Tbool */
-               propagate_types(b->left, c, ok, Tbool, 0);
-               propagate_types(b->right, c, ok, Tbool, 0);
-               if (type && type != Tbool) {
+               propagate_types(b->left, c, perr, Tbool, 0);
+               propagate_types(b->right, c, perr, Tbool, 0);
+               if (type && type != Tbool)
                        type_err(c, "error: %1 operation found where %2 expected", prog,
                                   Tbool, 0, type);
-                       *ok = 0;
-               }
+               *perr |= Erval;
                return Tbool;
 
 ###### interp binode cases
        case And:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
-               rv.bool = rv.bool && right.bool;
+               rv = interp_exec(c, b->left, &rvtype);
+               if (rv.bool)
+                       rv = interp_exec(c, b->right, NULL);
                break;
        case Or:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
-               rv.bool = rv.bool || right.bool;
+               rv = interp_exec(c, b->left, &rvtype);
+               if (!rv.bool)
+                       rv = interp_exec(c, b->right, NULL);
                break;
        case Not:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                rv.bool = !rv.bool;
                break;
 
 ### Expressions: Comparison
 
-Of slightly higher precedence that Boolean expressions are
-Comparisons.
-A comparison takes arguments of any type, but the two types must be
-the same.
+Of slightly higher precedence that Boolean expressions are Comparisons.
+A comparison takes arguments of any comparable type, but the two types
+must be the same.
 
 To simplify the parsing we introduce an `eop` which can record an
-expression operator.
+expression operator, and the `CMPop` non-terminal will match one of them.
 
 ###### ast
        struct eop {
@@ -1883,25 +4117,27 @@ expression operator.
        Eql,
        NEql,
 
-###### other BFact
-       | Expr CMPop Expr ${ {
-                       struct binode *b = new(binode);
-                       b->op = $2.op;
-                       b->left = $<1;
-                       b->right = $<3;
-                       $0 = b;
+###### declare terminals
+       $LEFT < > <= >= == != CMPop
+
+###### expression grammar
+       | Expression CMPop Expression ${ {
+               struct binode *b = new(binode);
+               b->op = $2.op;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
        } }$
-       | Expr ${ $0 = $<1; }$
 
 ###### Grammar
 
        $eop
-       CMPop ->   < ${ $0.op = Less; }$
-               |  > ${ $0.op = Gtr; }$
-               |  <= ${ $0.op = LessEq; }$
-               |  >= ${ $0.op = GtrEq; }$
-               |  == ${ $0.op = Eql; }$
-               |  != ${ $0.op = NEql; }$
+       CMPop ->  < ${ $0.op = Less; }$
+       |         > ${ $0.op = Gtr; }$
+       |         <= ${ $0.op = LessEq; }$
+       |         >= ${ $0.op = GtrEq; }$
+       |         == ${ $0.op = Eql; }$
+       |         != ${ $0.op = NEql; }$
 
 ###### print binode cases
 
@@ -1911,7 +4147,8 @@ expression operator.
        case GtrEq:
        case Eql:
        case NEql:
-               print_exec(b->left, -1, 0);
+               if (bracket) printf("(");
+               print_exec(b->left, -1, bracket);
                switch(b->op) {
                case Less:   printf(" < "); break;
                case LessEq: printf(" <= "); break;
@@ -1919,9 +4156,10 @@ expression operator.
                case GtrEq:  printf(" >= "); break;
                case Eql:    printf(" == "); break;
                case NEql:   printf(" != "); break;
-               default: abort();
+               default: abort();               // NOTEST
                }
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
                break;
 
 ###### propagate binode cases
@@ -1931,20 +4169,19 @@ expression operator.
        case GtrEq:
        case Eql:
        case NEql:
-               /* Both must match but not labels, result is Tbool */
-               t = propagate_types(b->left, c, ok, NULL, Rnolabel);
+               /* Both must match but not be labels, result is Tbool */
+               t = propagate_types(b->left, c, perr, NULL, 0);
                if (t)
-                       propagate_types(b->right, c, ok, t, 0);
+                       propagate_types(b->right, c, perr, t, 0);
                else {
-                       t = propagate_types(b->right, c, ok, NULL, Rnolabel);
-                       if (t)
-                               t = propagate_types(b->left, c, ok, t, 0);
+                       t = propagate_types(b->right, c, perr, NULL, 0); // NOTEST
+                       if (t)  // NOTEST
+                               t = propagate_types(b->left, c, perr, t, 0);    // NOTEST
                }
-               if (!vtype_compat(type, Tbool, 0)) {
+               if (!type_compat(type, Tbool, 0))
                        type_err(c, "error: Comparison returns %1 but %2 expected", prog,
                                    Tbool, rules, type);
-                       *ok = 0;
-               }
+               *perr |= Erval;
                return Tbool;
 
 ###### interp binode cases
@@ -1956,10 +4193,10 @@ expression operator.
        case NEql:
        {
                int cmp;
-               left = interp_exec(b->left);
-               right = interp_exec(b->right);
-               cmp = value_cmp(left, right);
-               rv.type = Tbool;
+               left = interp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               cmp = value_cmp(ltype, rtype, &left, &right);
+               rvtype = Tbool;
                switch (b->op) {
                case Less:      rv.bool = cmp <  0; break;
                case LessEq:    rv.bool = cmp <= 0; break;
@@ -1967,78 +4204,100 @@ expression operator.
                case GtrEq:     rv.bool = cmp >= 0; break;
                case Eql:       rv.bool = cmp == 0; break;
                case NEql:      rv.bool = cmp != 0; break;
-               default: rv.bool = 0; break;
+               default:        rv.bool = 0; break;     // NOTEST
                }
                break;
        }
 
-### Expressions: The rest
+### Expressions: Arithmetic etc.
 
-The remaining expressions with the highest precedence are arithmetic
-and string concatenation.  They are `Expr`, `Term`, and `Factor`.
-The `Factor` is where the `Value` and `Variable` that we already have
-are included.
+The remaining expressions with the highest precedence are arithmetic,
+string concatenation, string conversion, and testing.  String concatenation
+(`++`) has the same precedence as multiplication and division, but lower
+than the unary.
+
+Testing comes in two forms.  A single question mark (`?`) is a unary
+operator which converts come types into Boolean.  The general meaning is
+"is this a valid value" and there will be more uses as the language
+develops.  A double question-mark (`??`) is a binary operator (Choose),
+with the same precedence as multiplication, which returns the LHS if it
+tests successfully, else returns the RHS.
+
+String conversion is a temporary feature until I get a better type
+system.  `$` is a prefix operator which expects a string and returns
+a number.
 
 `+` and `-` are both infix and prefix operations (where they are
 absolute value and negation).  These have different operator names.
 
 We also have a 'Bracket' operator which records where parentheses were
-found.  This make it easy to reproduce these when printing.  Once
-precedence is handled better I might be able to discard this.
+found.  This makes it easy to reproduce these when printing.  Possibly I
+should only insert brackets were needed for precedence.  Putting
+parentheses around an expression converts it into a Term,
 
 ###### Binode types
        Plus, Minus,
-       Times, Divide,
-       Concat,
-       Absolute, Negate,
+       Times, Divide, Rem,
+       Concat, Choose,
+       Absolute, Negate, Test,
+       StringConv,
        Bracket,
 
-###### Grammar
+###### declare terminals
+       $LEFT + - Eop
+       $LEFT * / % ++ ?? Top
+       $LEFT Uop $ ?
+       $TERM ( )
+
+###### expression grammar
+       | Expression Eop Expression ${ {
+               struct binode *b = new(binode);
+               b->op = $2.op;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
+       } }$
 
-       $*exec
-       Expr -> Expr Eop Term ${ {
-                       struct binode *b = new(binode);
-                       b->op = $2.op;
-                       b->left = $<1;
-                       b->right = $<3;
-                       $0 = b;
-               } }$
-               | Term ${ $0 = $<1; }$
-
-       Term -> Term Top Factor ${ {
-                       struct binode *b = new(binode);
-                       b->op = $2.op;
-                       b->left = $<1;
-                       b->right = $<3;
-                       $0 = b;
-               } }$
-               | Factor ${ $0 = $<1; }$
-
-       Factor -> ( Expression ) ${ {
-                       struct binode *b = new_pos(binode, $1);
-                       b->op = Bracket;
-                       b->right = $<2;
-                       $0 = b;
-               } }$
-               | Uop Factor ${ {
-                       struct binode *b = new(binode);
-                       b->op = $1.op;
-                       b->right = $<2;
-                       $0 = b;
-               } }$
-               | Value ${ $0 = $<1; }$
-               | Variable ${ $0 = $<1; }$
+       | Expression Top Expression ${ {
+               struct binode *b = new(binode);
+               b->op = $2.op;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
+       } }$
+
+       | Uop Expression ${ {
+               struct binode *b = new(binode);
+               b->op = $1.op;
+               b->right = $<2;
+               $0 = b;
+       } }$
+
+###### term grammar
+
+       | ( Expression ) ${ {
+               struct binode *b = new_pos(binode, $1);
+               b->op = Bracket;
+               b->right = $<2;
+               $0 = b;
+       } }$
+
+###### Grammar
 
        $eop
-       Eop ->    + ${ $0.op = Plus; }$
-               | - ${ $0.op = Minus; }$
+       Eop ->   + ${ $0.op = Plus; }$
+       |        - ${ $0.op = Minus; }$
 
-       Uop ->    + ${ $0.op = Absolute; }$
-               | - ${ $0.op = Negate; }$
+       Uop ->   + ${ $0.op = Absolute; }$
+       |        - ${ $0.op = Negate; }$
+       |        $ ${ $0.op = StringConv; }$
+       |        ? ${ $0.op = Test; }$
 
-       Top ->    * ${ $0.op = Times; }$
-               | / ${ $0.op = Divide; }$
-               | ++ ${ $0.op = Concat; }$
+       Top ->   * ${ $0.op = Times; }$
+       |        / ${ $0.op = Divide; }$
+       |        % ${ $0.op = Rem; }$
+       |        ++ ${ $0.op = Concat; }$
+       |        ?? ${ $0.op = Choose; }$
 
 ###### print binode cases
        case Plus:
@@ -2046,109 +4305,215 @@ precedence is handled better I might be able to discard this.
        case Times:
        case Divide:
        case Concat:
-               print_exec(b->left, indent, 0);
+       case Rem:
+       case Choose:
+               if (bracket) printf("(");
+               print_exec(b->left, indent, bracket);
                switch(b->op) {
-               case Plus:   printf(" + "); break;
-               case Minus:  printf(" - "); break;
-               case Times:  printf(" * "); break;
-               case Divide: printf(" / "); break;
-               case Concat: printf(" ++ "); break;
-               default: abort();
-               }
-               print_exec(b->right, indent, 0);
+               case Plus:   fputs(" + ", stdout); break;
+               case Minus:  fputs(" - ", stdout); break;
+               case Times:  fputs(" * ", stdout); break;
+               case Divide: fputs(" / ", stdout); break;
+               case Rem:    fputs(" % ", stdout); break;
+               case Concat: fputs(" ++ ", stdout); break;
+               case Choose: fputs(" ?? ", stdout); break;
+               default: abort();       // NOTEST
+               }                       // NOTEST
+               print_exec(b->right, indent, bracket);
+               if (bracket) printf(")");
                break;
        case Absolute:
-               printf("+");
-               print_exec(b->right, indent, 0);
-               break;
        case Negate:
-               printf("-");
-               print_exec(b->right, indent, 0);
+       case StringConv:
+       case Test:
+               if (bracket) printf("(");
+               switch (b->op) {
+               case Absolute:   fputs("+", stdout); break;
+               case Negate:     fputs("-", stdout); break;
+               case StringConv: fputs("$", stdout); break;
+               case Test:       fputs("?", stdout); break;
+               default: abort();       // NOTEST
+               }                       // NOTEST
+               print_exec(b->right, indent, bracket);
+               if (bracket) printf(")");
                break;
        case Bracket:
-               printf("(");
-               print_exec(b->right, indent, 0);
-               printf(")");
+               /* Avoid double brackets... */
+               if (!bracket) printf("(");
+               print_exec(b->right, indent, bracket);
+               if (!bracket) printf(")");
                break;
 
 ###### propagate binode cases
        case Plus:
        case Minus:
        case Times:
+       case Rem:
        case Divide:
                /* both must be numbers, result is Tnum */
        case Absolute:
        case Negate:
                /* as propagate_types ignores a NULL,
                 * unary ops fit here too */
-               propagate_types(b->left, c, ok, Tnum, 0);
-               propagate_types(b->right, c, ok, Tnum, 0);
-               if (!vtype_compat(type, Tnum, 0)) {
+               propagate_types(b->left, c, perr, Tnum, 0);
+               propagate_types(b->right, c, perr, Tnum, 0);
+               if (!type_compat(type, Tnum, 0))
                        type_err(c, "error: Arithmetic returns %1 but %2 expected", prog,
                                   Tnum, rules, type);
-                       *ok = 0;
-               }
+               *perr |= Erval;
                return Tnum;
 
        case Concat:
                /* both must be Tstr, result is Tstr */
-               propagate_types(b->left, c, ok, Tstr, 0);
-               propagate_types(b->right, c, ok, Tstr, 0);
-               if (!vtype_compat(type, Tstr, 0)) {
+               propagate_types(b->left, c, perr, Tstr, 0);
+               propagate_types(b->right, c, perr, Tstr, 0);
+               if (!type_compat(type, Tstr, 0))
                        type_err(c, "error: Concat returns %1 but %2 expected", prog,
                                   Tstr, rules, type);
-                       *ok = 0;
-               }
+               *perr |= Erval;
                return Tstr;
 
+       case StringConv:
+               /* op must be string, result is number */
+               propagate_types(b->left, c, perr, Tstr, 0);
+               if (!type_compat(type, Tnum, 0))
+                       type_err(c,
+                         "error: Can only convert string to number, not %1",
+                               prog, type, 0, NULL);
+               *perr |= Erval;
+               return Tnum;
+
+       case Test:
+               /* LHS must support ->test, result is Tbool */
+               t = propagate_types(b->right, c, perr, NULL, 0);
+               if (!t || !t->test)
+                       type_err(c, "error: '?' requires a testable value, not %1",
+                                prog, t, 0, NULL);
+               *perr |= Erval;
+               return Tbool;
+
+       case Choose:
+               /* LHS and RHS must match and are returned. Must support
+                * ->test
+                */
+               t = propagate_types(b->left, c, perr, type, rules);
+               t = propagate_types(b->right, c, perr, t, rules);
+               if (t && t->test == NULL)
+                       type_err(c, "error: \"??\" requires a testable value, not %1",
+                                prog, t, 0, NULL);
+               *perr |= Erval;
+               return t;
+
        case Bracket:
-               return propagate_types(b->right, c, ok, type, 0);
+               return propagate_types(b->right, c, perr, type, rules);
 
 ###### interp binode cases
 
        case Plus:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_add(rv.num, rv.num, right.num);
                break;
        case Minus:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_sub(rv.num, rv.num, right.num);
                break;
        case Times:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_mul(rv.num, rv.num, right.num);
                break;
        case Divide:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_div(rv.num, rv.num, right.num);
                break;
+       case Rem: {
+               mpz_t l, r, rem;
+
+               left = interp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               mpz_init(l); mpz_init(r); mpz_init(rem);
+               mpz_tdiv_q(l, mpq_numref(left.num), mpq_denref(left.num));
+               mpz_tdiv_q(r, mpq_numref(right.num), mpq_denref(right.num));
+               mpz_tdiv_r(rem, l, r);
+               val_init(Tnum, &rv);
+               mpq_set_z(rv.num, rem);
+               mpz_clear(r); mpz_clear(l); mpz_clear(rem);
+               rvtype = ltype;
+               break;
+       }
        case Negate:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                mpq_neg(rv.num, rv.num);
                break;
        case Absolute:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                mpq_abs(rv.num, rv.num);
                break;
        case Bracket:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                break;
        case Concat:
-               left = interp_exec(b->left);
-               right = interp_exec(b->right);
-               rv.type = Tstr;
+               left = interp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               rvtype = Tstr;
                rv.str = text_join(left.str, right.str);
                break;
+       case StringConv:
+               right = interp_exec(c, b->right, &rvtype);
+               rtype = Tstr;
+               rvtype = Tnum;
+
+               struct text tx = right.str;
+               char tail[3] = "";
+               int neg = 0;
+               if (tx.txt[0] == '-') {
+                       neg = 1;
+                       tx.txt++;
+                       tx.len--;
+               }
+               if (number_parse(rv.num, tail, tx) == 0)
+                       mpq_init(rv.num);
+               else if (neg)
+                       mpq_neg(rv.num, rv.num);
+               if (tail[0])
+                       printf("Unsupported suffix: %.*s\n", tx.len, tx.txt);
+
+               break;
+       case Test:
+               right = interp_exec(c, b->right, &rtype);
+               rvtype = Tbool;
+               rv.bool = !!rtype->test(rtype, &right);
+               break;
+       case Choose:
+               left = interp_exec(c, b->left, &ltype);
+               if (ltype->test(ltype, &left)) {
+                       rv = left;
+                       rvtype = ltype;
+                       ltype = NULL;
+               } else
+                       rv = interp_exec(c, b->right, &rvtype);
+               break;
+
+###### value functions
+
+       static struct text text_join(struct text a, struct text b)
+       {
+               struct text rv;
+               rv.len = a.len + b.len;
+               rv.txt = malloc(rv.len);
+               memcpy(rv.txt, a.txt, a.len);
+               memcpy(rv.txt+a.len, b.txt, b.len);
+               return rv;
+       }
 
 ### Blocks, Statements, and Statement lists.
 
 Now that we have expressions out of the way we need to turn to
 statements.  There are simple statements and more complex statements.
-Simple statements do not contain newlines, complex statements do.
+Simple statements do not contain (syntactic) newlines, complex statements do.
 
 Statements often come in sequences and we have corresponding simple
 statement lists and complex statement lists.
@@ -2172,7 +4537,7 @@ confusion, so I'm not set on it yet.
 
 A simple statement list needs no extra syntax.  A complex statement
 list has two syntactic forms.  It can be enclosed in braces (much like
-C blocks), or it can be introduced by a colon and continue until an
+C blocks), or it can be introduced by an indent and continue until an
 unindented newline (much like Python blocks).  With this extra syntax
 it is referred to as a block.
 
@@ -2183,110 +4548,117 @@ contains simple statements.  So both of:
 
        if condition { a=b; print f }
 
-are valid.
-
-In either case the list is constructed from a `binode` list with
-`Block` as the operator.  When parsing the list it is most convenient
-to append to the end, so a list is a list and a statement.  When using
-the list it is more convenient to consider a list to be a statement
-and a list.  So we need a function to re-order a list.
-`reorder_bilist` serves this purpose.
+are valid.  In either case the list is constructed from a `binode` list
+with `Block` as the operator.
 
 The only stand-alone statement we introduce at this stage is `pass`
 which does nothing and is represented as a `NULL` pointer in a `Block`
-list.
+list.  Other stand-alone statements will follow once the infrastructure
+is in-place.
+
+As many statements will use binodes, we declare a binode pointer 'b' in
+the common header for all reductions to use.
+
+###### Parser: reduce
+       struct binode *b;
 
 ###### Binode types
        Block,
 
 ###### Grammar
 
-       $void
-       OptNL -> Newlines
-               |
-
-       Newlines -> NEWLINE
-               | Newlines NEWLINE
+       $TERM { } ;
 
        $*binode
-       Open -> {
-               | NEWLINE {
-       Close -> }
-               | NEWLINE }
-       Block -> Open Statementlist Close ${ $0 = $<2; }$
-               | Open Newlines Statementlist Close ${ $0 = $<3; }$
-               | Open SimpleStatements } ${ $0 = reorder_bilist($<2); }$
-               | Open Newlines SimpleStatements } ${ $0 = reorder_bilist($<3); }$
-               | : Statementlist ${ $0 = $<2; }$
-               | : SimpleStatements ${ $0 = reorder_bilist($<2); }$
-
-       Statementlist -> ComplexStatements ${ $0 = reorder_bilist($<1); }$
+       Block -> { IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+       |        { SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+       |        SimpleStatements ; ${ $0 = reorder_bilist($<SS); }$
+       |        SimpleStatements EOL ${ $0 = reorder_bilist($<SS); }$
+       |        IN OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       OpenBlock -> OpenScope { IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+       |        OpenScope { SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+       |        OpenScope SimpleStatements ; ${ $0 = reorder_bilist($<SS); }$
+       |        OpenScope SimpleStatements EOL ${ $0 = reorder_bilist($<SS); }$
+       |        IN OpenScope OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       UseBlock -> { IN OpenScope OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+       |        { OpenScope SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+       |        IN OpenScope OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       ColonBlock -> { IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+       |        { SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+       |        : SimpleStatements ; ${ $0 = reorder_bilist($<SS); }$
+       |        : SimpleStatements EOL ${ $0 = reorder_bilist($<SS); }$
+       |        : IN OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       Statementlist -> ComplexStatements ${ $0 = reorder_bilist($<CS); }$
 
        ComplexStatements -> ComplexStatements ComplexStatement ${
+               if ($2 == NULL) {
+                       $0 = $<1;       // NOTEST - impossible
+               } else {
+                       $0 = new(binode);
+                       $0->op = Block;
+                       $0->left = $<1;
+                       $0->right = $<2;
+               }
+       }$
+       | ComplexStatement ${
+               if ($1 == NULL) {
+                       $0 = NULL;      // NOTEST - impossible
+               } else {
+                       $0 = new(binode);
+                       $0->op = Block;
+                       $0->left = NULL;
+                       $0->right = $<1;
+               }
+       }$
+
+       $*exec
+       ComplexStatement -> SimpleStatements Newlines ${
+               $0 = reorder_bilist($<SS);
+       }$
+       |  SimpleStatements ; Newlines ${
+               $0 = reorder_bilist($<SS);
+       }$
+       ## ComplexStatement Grammar
+
+       $*binode
+       SimpleStatements -> SimpleStatements ; SimpleStatement ${
                $0 = new(binode);
                $0->op = Block;
                $0->left = $<1;
-               $0->right = $<2;
-               }$
-               | ComplexStatements NEWLINE ${ $0 = $<1; }$
-               | ComplexStatement ${
+               $0->right = $<3;
+       }$
+       | SimpleStatement ${
                $0 = new(binode);
                $0->op = Block;
                $0->left = NULL;
                $0->right = $<1;
-               }$
+       }$
 
+       $TERM pass
        $*exec
-       ComplexStatement -> SimpleStatements NEWLINE ${
-                       $0 = reorder_bilist($<1);
-                       }$
-               ## ComplexStatement Grammar
-
-       $*binode
-       SimpleStatements -> SimpleStatements ; SimpleStatement ${
-                       $0 = new(binode);
-                       $0->op = Block;
-                       $0->left = $<1;
-                       $0->right = $<3;
-                       }$
-               | SimpleStatement ${
-                       $0 = new(binode);
-                       $0->op = Block;
-                       $0->left = NULL;
-                       $0->right = $<1;
-                       }$
-               | SimpleStatements ; ${ $0 = $<1; }$
-
        SimpleStatement -> pass ${ $0 = NULL; }$
-               ## SimpleStatement Grammar
+       | ERROR ${ tok_err(c, "Syntax error in statement", &$1); }$
+       ## SimpleStatement Grammar
 
 ###### print binode cases
        case Block:
-               if (indent < 0) {
-                       // simple statement
-                       if (b->left == NULL)
-                               printf("pass");
-                       else
-                               print_exec(b->left, indent, 0);
-                       if (b->right) {
-                               printf("; ");
-                               print_exec(b->right, indent, 0);
-                       }
-               } else {
-                       // block, one per line
-                       if (b->left == NULL)
-                               do_indent(indent, "pass\n");
-                       else
-                               print_exec(b->left, indent, bracket);
-                       if (b->right)
-                               print_exec(b->right, indent, bracket);
-               }
+               // block, one per line
+               if (b->left == NULL)
+                       do_indent(indent, "pass\n");
+               else
+                       print_exec(b->left, indent, bracket);
+               if (b->right)
+                       print_exec(b->right, indent, bracket);
                break;
 
 ###### propagate binode cases
        case Block:
        {
-               /* If any statement returns something other then Tnone
+               /* If any statement returns something other than Tnone
                 * or Tbool then all such must return same type.
                 * As each statement may be Tnone or something else,
                 * we must always pass NULL (unknown) down, otherwise an incorrect
@@ -2296,17 +4668,22 @@ list.
                struct binode *e;
 
                for (e = b; e; e = cast(binode, e->right)) {
-                       t = propagate_types(e->left, c, ok, NULL, rules);
-                       if ((rules & Rboolok) && t == Tbool)
+                       *perr |= *perr_local;
+                       *perr_local = 0;
+                       t = propagate_types(e->left, c, perr_local, NULL, rules);
+                       if ((rules & Rboolok) && (t == Tbool || t == Tnone))
+                               t = NULL;
+                       if (t == Tnone && e->right)
+                               /* Only the final statement *must* return a value
+                                * when not Rboolok
+                                */
                                t = NULL;
-                       if (t && t != Tnone && t != Tbool) {
+                       if (t) {
                                if (!type)
                                        type = t;
-                               else if (t != type) {
-                                       type_err(c, "error: expected %1%r, found %2",
+                               else if (t != type)
+                                       type_err(c, "error: expected %1, found %2",
                                                 e->left, type, rules, t);
-                                       *ok = 0;
-                               }
                        }
                }
                return type;
@@ -2314,10 +4691,10 @@ list.
 
 ###### interp binode cases
        case Block:
-               while (rv.type == Tnone &&
+               while (rvtype == Tnone &&
                       b) {
                        if (b->left)
-                               rv = interp_exec(b->left);
+                               rv = interp_exec(c, b->left, &rvtype);
                        b = cast(binode, b->right);
                }
                break;
@@ -2328,59 +4705,52 @@ list.
 expressions and prints the values separated by spaces and terminated
 by a newline.  No control of formatting is possible.
 
-`print` faces the same list-ordering issue as blocks, and uses the
-same solution.
+`print` uses `ExpressionList` to collect the expressions and stores them
+on the left side of a `Print` binode unless there is a trailing comma
+when the list is stored on the `right` side and no trailing newline is
+printed.
 
 ###### Binode types
        Print,
 
+##### declare terminals
+       $TERM print
+
 ###### SimpleStatement Grammar
 
        | print ExpressionList ${
-               $0 = reorder_bilist($<2);
-       }$
-       | print ExpressionList , ${
-               $0 = new(binode);
-               $0->op = Print;
-               $0->right = NULL;
-               $0->left = $<2;
-               $0 = reorder_bilist($0);
+               $0 = b = new_pos(binode, $1);
+               b->op = Print;
+               b->right = NULL;
+               b->left = reorder_bilist($<EL);
        }$
+       | print ExpressionList , ${ {
+               $0 = b = new_pos(binode, $1);
+               b->op = Print;
+               b->right = reorder_bilist($<EL);
+               b->left = NULL;
+       } }$
        | print ${
-               $0 = new(binode);
-               $0->op = Print;
-               $0->right = NULL;
+               $0 = b = new_pos(binode, $1);
+               b->op = Print;
+               b->left = NULL;
+               b->right = NULL;
        }$
 
-###### Grammar
-
-       $*binode
-       ExpressionList -> ExpressionList , Expression ${
-               $0 = new(binode);
-               $0->op = Print;
-               $0->left = $<1;
-               $0->right = $<3;
-               }$
-               | Expression ${
-                       $0 = new(binode);
-                       $0->op = Print;
-                       $0->left = NULL;
-                       $0->right = $<1;
-               }$
-
 ###### print binode cases
 
        case Print:
                do_indent(indent, "print");
-               while (b) {
-                       if (b->left) {
-                               printf(" ");
-                               print_exec(b->left, -1, 0);
-                               if (b->right)
-                                       printf(",");
-                       }
-                       b = cast(binode, b->right);
+               b2 = cast(binode, b->left ?: b->right);
+               while (b2) {
+                       printf(" ");
+                       print_exec(b2->left, -1, bracket);
+                       if (b2->right)
+                               printf(",");
+                       b2 = cast(binode, b2->right);
                }
+               if (b->right)
+                       printf(",");
                if (indent >= 0)
                        printf("\n");
                break;
@@ -2389,73 +4759,88 @@ same solution.
 
        case Print:
                /* don't care but all must be consistent */
-               propagate_types(b->left, c, ok, NULL, Rnolabel);
-               propagate_types(b->right, c, ok, NULL, Rnolabel);
+               if (b->left)
+                       b = cast(binode, b->left);
+               else
+                       b = cast(binode, b->right);
+               while (b) {
+                       propagate_types(b->left, c, perr_local, NULL, 0);
+                       b = cast(binode, b->right);
+               }
                break;
 
 ###### interp binode cases
 
        case Print:
        {
-               char sep = 0;
-               int eol = 1;
-               for ( ; b; b = cast(binode, b->right))
-                       if (b->left) {
-                               if (sep)
-                                       putchar(sep);
-                               left = interp_exec(b->left);
-                               print_value(left);
-                               free_value(left);
-                               if (b->right)
-                                       sep = ' ';
-                       } else if (sep)
-                               eol = 0;
-               left.type = Tnone;
-               if (eol)
+               struct binode *b2 = cast(binode, b->left);
+               if (!b2)
+                       b2 = cast(binode, b->right);
+               for (; b2; b2 = cast(binode, b2->right)) {
+                       left = interp_exec(c, b2->left, &ltype);
+                       print_value(ltype, &left, stdout);
+                       free_value(ltype, &left);
+                       if (b2->right)
+                               putchar(' ');
+               }
+               if (b->right == NULL)
                        printf("\n");
+               ltype = Tnone;
                break;
        }
 
 ###### Assignment statement
 
 An assignment will assign a value to a variable, providing it hasn't
-be declared as a constant.  The analysis phase ensures that the type
+been declared as a constant.  The analysis phase ensures that the type
 will be correct so the interpreter just needs to perform the
 calculation.  There is a form of assignment which declares a new
-variable as well as assigning a value.  If a name is assigned before
-it is declared, and error will be raised as the name is created as
-`Tlabel` and it is illegal to assign to such names.
+variable as well as assigning a value.  If a name is used before
+it is declared, it is assumed to be a global constant which are allowed to
+be declared at any time.
 
 ###### Binode types
        Assign,
        Declare,
 
+###### declare terminals
+       $TERM =
+
 ###### SimpleStatement Grammar
-       | Variable = Expression ${ {
-                       struct var *v = cast(var, $1);
+       | Term = Expression ${
+               $0 = b= new(binode);
+               b->op = Assign;
+               b->left = $<1;
+               b->right = $<3;
+       }$
+       | VariableDecl = Expression ${
+               $0 = b= new(binode);
+               b->op = Declare;
+               b->left = $<1;
+               b->right =$<3;
+       }$
 
-                       $0 = new(binode);
-                       $0->op = Assign;
-                       $0->left = $<1;
-                       $0->right = $<3;
-                       if (v->var && !v->var->constant) {
-                               /* FIXME error? */
-                       }
-               } }$
-       | VariableDecl Expression ${
-                       $0 = new(binode);
-                       $0->op = Declare;
-                       $0->left = $<1;
-                       $0->right =$<2;
-               }$
+       | VariableDecl ${
+               if ($1->var->where_set == NULL) {
+                       type_err(c,
+                                "Variable declared with no type or value: %v",
+                                $1, NULL, 0, NULL);
+                       free_var($1);
+               } else {
+                       $0 = b = new(binode);
+                       b->op = Declare;
+                       b->left = $<1;
+                       b->right = NULL;
+               }
+       }$
 
 ###### print binode cases
 
        case Assign:
                do_indent(indent, "");
-               print_exec(b->left, indent, 0);
+               print_exec(b->left, -1, bracket);
                printf(" = ");
-               print_exec(b->right, indent, 0);
+               print_exec(b->right, -1, bracket);
                if (indent >= 0)
                        printf("\n");
                break;
@@ -2464,21 +4849,24 @@ it is declared, and error will be raised as the name is created as
                {
                struct variable *v = cast(var, b->left)->var;
                do_indent(indent, "");
-               print_exec(b->left, indent, 0);
+               print_exec(b->left, -1, bracket);
                if (cast(var, b->left)->var->constant) {
-                       if (v->where_decl == v->where_set)
-                               printf("::%.*s = ", v->val.type->name.len,
-                                      v->val.type->name.txt);
-                       else
-                               printf(" ::= ");
+                       printf("::");
+                       if (v->explicit_type) {
+                               type_print(v->type, stdout);
+                               printf(" ");
+                       }
                } else {
-                       if (v->where_decl == v->where_set)
-                               printf(":%.*s = ", v->val.type->name.len,
-                                      v->val.type->name.txt);
-                       else
-                               printf(" := ");
+                       printf(":");
+                       if (v->explicit_type) {
+                               type_print(v->type, stdout);
+                               printf(" ");
+                       }
+               }
+               if (b->right) {
+                       printf("= ");
+                       print_exec(b->right, -1, bracket);
                }
-               print_exec(b->right, indent, 0);
                if (indent >= 0)
                        printf("\n");
                }
@@ -2488,18 +4876,53 @@ it is declared, and error will be raised as the name is created as
 
        case Assign:
        case Declare:
-               /* Both must match and not be labels, result is Tnone */
-               t = propagate_types(b->left, c, ok, NULL, Rnolabel);
+               /* Both must match, or left may be ref and right an lval
+                * Type must support 'dup',
+                * For Assign, left must not be constant.
+                * result is Tnone
+                */
+               *perr &= ~(Erval | Econst);
+               t = propagate_types(b->left, c, perr, NULL, 0);
+               if (!b->right)
+                       return Tnone;
+
                if (t) {
-                       if (propagate_types(b->right, c, ok, t, 0) != t)
-                               if (b->left->type == Xvar)
-                                       type_err(c, "info: variable '%v' was set as %1 here.",
-                                                cast(var, b->left)->var->where_set, t, rules, Tnone);
+                       struct type *t2 = propagate_types(b->right, c, perr_local,
+                                                         t, Rrefok);
+                       if (!t2 || t2 == t || (*perr_local & Efail))
+                               ; // No more effort needed
+                       else if (t->free == reference_free &&
+                                t->reference.referent == t2 &&
+                                !(*perr_local & Erval))
+                               b->right = take_addr(b->right);
+                       else if (t->free == reference_free &&
+                                t->reference.referent == t2 &&
+                                (*perr_local & Erval))
+                               type_err(c, "error: Cannot assign an rval to a reference.",
+                                        b, NULL, 0, NULL);
                } else {
-                       t = propagate_types(b->right, c, ok, NULL, Rnolabel);
+                       t = propagate_types(b->right, c, perr_local, NULL, 0);
                        if (t)
-                               propagate_types(b->left, c, ok, t, 0);
+                               propagate_types(b->left, c, perr, t, 0);
+               }
+               if (*perr & Erval)
+                       type_err(c, "error: cannot assign to an rval", b,
+                                NULL, 0, NULL);
+               else if (b->op == Assign && (*perr & Econst)) {
+                       type_err(c, "error: Cannot assign to a constant: %v",
+                                b->left, NULL, 0, NULL);
+                       if (b->left->type == Xvar) {
+                               struct var *var = cast(var, b->left);
+                               struct variable *v = var->var;
+                               type_err(c, "info: name was defined as a constant here",
+                                        v->where_decl, NULL, 0, NULL);
+                       }
                }
+               if (t && t->dup == NULL && !(*perr_local & Emaycopy))
+                       type_err(c, "error: cannot assign value of type %1", b, t, 0, NULL);
+               if (b->left->type == Xvar && (*perr_local & Efail))
+                       type_err(c, "info: variable '%v' was set as %1 here.",
+                                cast(var, b->left)->var->where_set, t, rules, NULL);
                return Tnone;
 
                break;
@@ -2507,40 +4930,52 @@ it is declared, and error will be raised as the name is created as
 ###### interp binode cases
 
        case Assign:
+               lleft = linterp_exec(c, b->left, &ltype);
+               if (lleft)
+                       dinterp_exec(c, b->right, lleft, ltype, 1);
+               ltype = Tnone;
+               break;
+
        case Declare:
        {
                struct variable *v = cast(var, b->left)->var;
-               if (v->merged)
-                       v = v->merged;
-               right = interp_exec(b->right);
-               free_value(v->val);
-               v->val = right;
-               right.type = NULL;
+               struct value *val;
+               v = v->merged;
+               val = var_value(c, v);
+               if (v->type->prepare_type)
+                       v->type->prepare_type(c, v->type, 0);
+               if (!b->right)
+                       val_init(v->type, val);
+               else
+                       dinterp_exec(c, b->right, val, v->type, 0);
                break;
        }
 
 ### The `use` statement
 
-The `use` statement is the last "simple" statement.  It is needed when
-the condition in a conditional statement is a block.  `use` works much
-like `return` in C, but only completes the `condition`, not the whole
-function.
+The `use` statement is the last "simple" statement.  It is needed when a
+statement block can return a value.  This includes the body of a
+function which has a return type, and the "condition" code blocks in
+`if`, `while`, and `switch` statements.
 
 ###### Binode types
        Use,
 
+###### declare terminals
+       $TERM use
+
 ###### SimpleStatement Grammar
        | use Expression ${
-               $0 = new_pos(binode, $1);
-               $0->op = Use;
-               $0->right = $<2;
+               $0 = b = new_pos(binode, $1);
+               b->op = Use;
+               b->right = $<2;
        }$
 
 ###### print binode cases
 
        case Use:
                do_indent(indent, "use ");
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
                if (indent >= 0)
                        printf("\n");
                break;
@@ -2549,12 +4984,12 @@ function.
 
        case Use:
                /* result matches value */
-               return propagate_types(b->right, c, ok, type, 0);
+               return propagate_types(b->right, c, perr, type, 0);
 
 ###### interp binode cases
 
        case Use:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                break;
 
 ### The Conditional Statement
@@ -2619,7 +5054,15 @@ the type of the `whilepart` code block is the reason for the
 `Rboolok` flag which is passed to `propagate_types()`.
 
 The `cond_statement` cannot fit into a `binode` so a new `exec` is
-defined.
+defined.  As there are two scopes which cover multiple parts - one for
+the whole statement and one for "while" and "do" - and as we will use
+the 'struct exec' to track scopes, we actually need two new types of
+exec.  One is a `binode` for the looping part, the rest is the
+`cond_statement`.  The `cond_statement` will use an auxilliary `struct
+casepart` to track a list of case parts.
+
+###### Binode types
+       Loop
 
 ###### exec type
        Xcond_statement,
@@ -2632,7 +5075,8 @@ defined.
        };
        struct cond_statement {
                struct exec;
-               struct exec *forpart, *condpart, *dopart, *thenpart, *elsepart;
+               struct exec *forpart, *condpart, *thenpart, *elsepart;
+               struct binode *looppart;
                struct casepart *casepart;
        };
 
@@ -2656,7 +5100,7 @@ defined.
                        return;
                free_exec(s->forpart);
                free_exec(s->condpart);
-               free_exec(s->dopart);
+               free_exec(s->looppart);
                free_exec(s->thenpart);
                free_exec(s->elsepart);
                free_casepart(s->casepart);
@@ -2669,155 +5113,182 @@ defined.
 ###### ComplexStatement Grammar
        | CondStatement ${ $0 = $<1; }$
 
+###### declare terminals
+       $TERM for then while do
+       $TERM else
+       $TERM switch case
+
 ###### Grammar
 
        $*cond_statement
-       // both ForThen and Whilepart open scopes, and CondSuffix only
-       // closes one - so in the first branch here we have another to close.
-       CondStatement -> ForThen WhilePart CondSuffix ${
-                       $0 = $<3;
-                       $0->forpart = $1.forpart; $1.forpart = NULL;
-                       $0->thenpart = $1.thenpart; $1.thenpart = NULL;
-                       $0->condpart = $2.condpart; $2.condpart = NULL;
-                       $0->dopart = $2.dopart; $2.dopart = NULL;
-                       var_block_close(config2context(config), CloseSequential);
-                       }$
-               | WhilePart CondSuffix ${
-                       $0 = $<2;
-                       $0->condpart = $1.condpart; $1.condpart = NULL;
-                       $0->dopart = $1.dopart; $1.dopart = NULL;
-                       }$
-               | SwitchPart CondSuffix ${
-                       $0 = $<2;
-                       $0->condpart = $<1;
-                       }$
-               | IfPart IfSuffix ${
-                       $0 = $<2;
-                       $0->condpart = $1.condpart; $1.condpart = NULL;
-                       $0->thenpart = $1.thenpart; $1.thenpart = NULL;
-                       // This is where we close an "if" statement
-                       var_block_close(config2context(config), CloseSequential);
-                       }$
+       // A CondStatement must end with EOL, as does CondSuffix and
+       // IfSuffix.
+       // ForPart, ThenPart, SwitchPart, CasePart are non-empty and
+       // may or may not end with EOL
+       // WhilePart and IfPart include an appropriate Suffix
+
+       // ForPart, SwitchPart, and IfPart open scopes, o we have to close
+       // them.  WhilePart opens and closes its own scope.
+       CondStatement -> ForPart OptNL ThenPart OptNL WhilePart CondSuffix ${
+               $0 = $<CS;
+               $0->forpart = $<FP;
+               $0->thenpart = $<TP;
+               $0->looppart = $<WP;
+               var_block_close(c, CloseSequential, $0);
+       }$
+       | ForPart OptNL WhilePart CondSuffix ${
+               $0 = $<CS;
+               $0->forpart = $<FP;
+               $0->looppart = $<WP;
+               var_block_close(c, CloseSequential, $0);
+       }$
+       | WhilePart CondSuffix ${
+               $0 = $<CS;
+               $0->looppart = $<WP;
+       }$
+       | SwitchPart OptNL CasePart CondSuffix ${
+               $0 = $<CS;
+               $0->condpart = $<SP;
+               $CP->next = $0->casepart;
+               $0->casepart = $<CP;
+               var_block_close(c, CloseSequential, $0);
+       }$
+       | SwitchPart : IN OptNL CasePart CondSuffix OUT Newlines ${
+               $0 = $<CS;
+               $0->condpart = $<SP;
+               $CP->next = $0->casepart;
+               $0->casepart = $<CP;
+               var_block_close(c, CloseSequential, $0);
+       }$
+       | IfPart IfSuffix ${
+               $0 = $<IS;
+               $0->condpart = $IP.condpart; $IP.condpart = NULL;
+               $0->thenpart = $IP.thenpart; $IP.thenpart = NULL;
+               // This is where we close an "if" statement
+               var_block_close(c, CloseSequential, $0);
+       }$
 
        CondSuffix -> IfSuffix ${
-                       $0 = $<1;
-                       // This is where we close scope of the whole
-                       // "for" or "while" statement
-                       var_block_close(config2context(config), CloseSequential);
-               }$
-               | CasePart CondSuffix ${
-                       $0 = $<2;
-                       $1->next = $0->casepart;
-                       $0->casepart = $<1;
-               }$
-
-       $*casepart
-       CasePart -> Newlines case Expression OpenScope Block ${
-                       $0 = calloc(1,sizeof(struct casepart));
-                       $0->value = $<3;
-                       $0->action = $<5;
-                       var_block_close(config2context(config), CloseParallel);
-               }$
-               | case Expression OpenScope Block ${
-                       $0 = calloc(1,sizeof(struct casepart));
-                       $0->value = $<2;
-                       $0->action = $<4;
-                       var_block_close(config2context(config), CloseParallel);
-               }$
+               $0 = $<1;
+       }$
+       | Newlines CasePart CondSuffix ${
+               $0 = $<CS;
+               $CP->next = $0->casepart;
+               $0->casepart = $<CP;
+       }$
+       | CasePart CondSuffix ${
+               $0 = $<CS;
+               $CP->next = $0->casepart;
+               $0->casepart = $<CP;
+       }$
 
-       $*cond_statement
        IfSuffix -> Newlines ${ $0 = new(cond_statement); }$
-               | Newlines else OpenScope Block ${
-                       $0 = new(cond_statement);
-                       $0->elsepart = $<4;
-                       var_block_close(config2context(config), CloseElse);
-               }$
-               | else OpenScope Block ${
-                       $0 = new(cond_statement);
-                       $0->elsepart = $<3;
-                       var_block_close(config2context(config), CloseElse);
-               }$
-               | Newlines else OpenScope CondStatement ${
-                       $0 = new(cond_statement);
-                       $0->elsepart = $<4;
-                       var_block_close(config2context(config), CloseElse);
-               }$
-               | else OpenScope CondStatement ${
-                       $0 = new(cond_statement);
-                       $0->elsepart = $<3;
-                       var_block_close(config2context(config), CloseElse);
-               }$
+       | Newlines ElsePart ${ $0 = $<EP; }$
+       | ElsePart ${$0 = $<EP; }$
+
+       ElsePart -> else OpenBlock Newlines ${
+               $0 = new(cond_statement);
+               $0->elsepart = $<OB;
+               var_block_close(c, CloseElse, $0->elsepart);
+       }$
+       | else OpenScope CondStatement ${
+               $0 = new(cond_statement);
+               $0->elsepart = $<CS;
+               var_block_close(c, CloseElse, $0->elsepart);
+       }$
 
+       $*casepart
+       CasePart -> case Expression OpenScope ColonBlock ${
+               $0 = calloc(1,sizeof(struct casepart));
+               $0->value = $<Ex;
+               $0->action = $<Bl;
+               var_block_close(c, CloseParallel, $0->action);
+       }$
 
        $*exec
-       // These scopes are closed in CondSuffix
-       ForPart -> for OpenScope SimpleStatements ${
-                       $0 = reorder_bilist($<3);
-               }$
-               |  for OpenScope Block ${
-                       $0 = $<3;
-               }$
-
-       ThenPart -> then OpenScope SimpleStatements ${
-                       $0 = reorder_bilist($<3);
-                       var_block_close(config2context(config), CloseSequential);
-               }$
-               |  then OpenScope Block ${
-                       $0 = $<3;
-                       var_block_close(config2context(config), CloseSequential);
-               }$
-
-       ThenPartNL -> ThenPart OptNL ${
-                       $0 = $<1;
-               }$
-
-       // This scope is closed in CondSuffix
-       WhileHead -> while OpenScope Block ${
-               $0 = $<3;
-               }$
+       // These scopes are closed in CondStatement
+       ForPart -> for OpenBlock ${
+               $0 = $<Bl;
+       }$
+
+       ThenPart -> then OpenBlock ${
+               $0 = $<OB;
+               var_block_close(c, CloseSequential, $0);
+       }$
+
+       $*binode
+       // This scope is closed in CondStatement
+       WhilePart -> while UseBlock OptNL do OpenBlock ${
+               $0 = new(binode);
+               $0->op = Loop;
+               $0->left = $<UB;
+               $0->right = $<OB;
+               var_block_close(c, CloseSequential, $0->right);
+               var_block_close(c, CloseSequential, $0);
+       }$
+       | while OpenScope Expression OpenScope ColonBlock ${
+               $0 = new(binode);
+               $0->op = Loop;
+               $0->left = $<Exp;
+               $0->right = $<CB;
+               var_block_close(c, CloseSequential, $0->right);
+               var_block_close(c, CloseSequential, $0);
+       }$
 
        $cond_statement
-       ForThen -> ForPart OptNL ThenPartNL ${
-                       $0.forpart = $<1;
-                       $0.thenpart = $<3;
-               }$
-               | ForPart OptNL ${
-                       $0.forpart = $<1;
-               }$
-
-       // This scope is closed in CondSuffix
-       WhilePart -> while OpenScope Expression Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.dopart = $<4;
-               }$
-               | WhileHead OptNL do Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<1;
-                       $0.dopart = $<4;
-               }$
-
-       IfPart -> if OpenScope Expression OpenScope Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.thenpart = $<5;
-                       var_block_close(config2context(config), CloseParallel);
-               }$
-               | if OpenScope Block OptNL then OpenScope Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.thenpart = $<7;
-                       var_block_close(config2context(config), CloseParallel);
-               }$
+       IfPart -> if UseBlock OptNL then OpenBlock ${
+               $0.condpart = $<UB;
+               $0.thenpart = $<OB;
+               var_block_close(c, CloseParallel, $0.thenpart);
+       }$
+       | if OpenScope Expression OpenScope ColonBlock ${
+               $0.condpart = $<Ex;
+               $0.thenpart = $<CB;
+               var_block_close(c, CloseParallel, $0.thenpart);
+       }$
+       | if OpenScope Expression OpenScope OptNL then Block ${
+               $0.condpart = $<Ex;
+               $0.thenpart = $<Bl;
+               var_block_close(c, CloseParallel, $0.thenpart);
+       }$
 
        $*exec
-       // This scope is closed in CondSuffix
+       // This scope is closed in CondStatement
        SwitchPart -> switch OpenScope Expression ${
-                       $0 = $<3;
-               }$
-               | switch OpenScope Block ${
-                       $0 = $<3;
-               }$
+               $0 = $<Ex;
+       }$
+       | switch UseBlock ${
+               $0 = $<Bl;
+       }$
+
+###### print binode cases
+       case Loop:
+               if (b->left && b->left->type == Xbinode &&
+                   cast(binode, b->left)->op == Block) {
+                       if (bracket)
+                               do_indent(indent, "while {\n");
+                       else
+                               do_indent(indent, "while\n");
+                       print_exec(b->left, indent+1, bracket);
+                       if (bracket)
+                               do_indent(indent, "} do {\n");
+                       else
+                               do_indent(indent, "do\n");
+                       print_exec(b->right, indent+1, bracket);
+                       if (bracket)
+                               do_indent(indent, "}\n");
+               } else {
+                       do_indent(indent, "while ");
+                       print_exec(b->left, 0, bracket);
+                       if (bracket)
+                               printf(" {\n");
+                       else
+                               printf(":\n");
+                       print_exec(b->right, indent+1, bracket);
+                       if (bracket)
+                               do_indent(indent, "}\n");
+               }
+               break;
 
 ###### print exec cases
 
@@ -2827,44 +5298,19 @@ defined.
                struct casepart *cp;
                if (cs->forpart) {
                        do_indent(indent, "for");
-                       if (bracket) printf(" {\n"); else printf(":\n");
+                       if (bracket) printf(" {\n"); else printf("\n");
                        print_exec(cs->forpart, indent+1, bracket);
                        if (cs->thenpart) {
                                if (bracket)
                                        do_indent(indent, "} then {\n");
                                else
-                                       do_indent(indent, "then:\n");
+                                       do_indent(indent, "then\n");
                                print_exec(cs->thenpart, indent+1, bracket);
                        }
                        if (bracket) do_indent(indent, "}\n");
                }
-               if (cs->dopart) {
-                       // a loop
-                       if (cs->condpart && cs->condpart->type == Xbinode &&
-                           cast(binode, cs->condpart)->op == Block) {
-                               if (bracket)
-                                       do_indent(indent, "while {\n");
-                               else
-                                       do_indent(indent, "while:\n");
-                               print_exec(cs->condpart, indent+1, bracket);
-                               if (bracket)
-                                       do_indent(indent, "} do {\n");
-                               else
-                                       do_indent(indent, "do:\n");
-                               print_exec(cs->dopart, indent+1, bracket);
-                               if (bracket)
-                                       do_indent(indent, "}\n");
-                       } else {
-                               do_indent(indent, "while ");
-                               print_exec(cs->condpart, 0, bracket);
-                               if (bracket)
-                                       printf(" {\n");
-                               else
-                                       printf(":\n");
-                               print_exec(cs->dopart, indent+1, bracket);
-                               if (bracket)
-                                       do_indent(indent, "}\n");
-                       }
+               if (cs->looppart) {
+                       print_exec(cs->looppart, indent, bracket);
                } else {
                        // a condition
                        if (cs->casepart)
@@ -2876,12 +5322,12 @@ defined.
                                if (bracket)
                                        printf(" {\n");
                                else
-                                       printf(":\n");
+                                       printf("\n");
                                print_exec(cs->condpart, indent+1, bracket);
                                if (bracket)
                                        do_indent(indent, "}\n");
                                if (cs->thenpart) {
-                                       do_indent(indent, "then:\n");
+                                       do_indent(indent, "then\n");
                                        print_exec(cs->thenpart, indent+1, bracket);
                                }
                        } else {
@@ -2915,7 +5361,7 @@ defined.
                        if (bracket)
                                printf(" {\n");
                        else
-                               printf(":\n");
+                               printf("\n");
                        print_exec(cs->elsepart, indent+1, bracket);
                        if (bracket)
                                do_indent(indent, "}\n");
@@ -2923,304 +5369,672 @@ defined.
                break;
        }
 
+###### propagate binode cases
+       case Loop:
+               propagate_types(b->right, c, perr_local, Tnone, 0);
+               return propagate_types(b->left, c, perr, type, rules);
+
 ###### propagate exec cases
        case Xcond_statement:
        {
-               // forpart and dopart must return Tnone
-               // thenpart must return Tnone if there is a dopart,
+               // forpart and looppart->right must return Tnone
+               // thenpart must return Tnone if there is a loopart,
                // otherwise it is like elsepart.
                // condpart must:
-               //    be bool if there is not casepart
+               //    be bool if there is no casepart
                //    match casepart->values if there is a switchpart
                //    either be bool or match casepart->value if there
                //             is a whilepart
-               // elsepart, casepart->action must match there return type
-               // expected of this statement.
+               // elsepart and casepart->action must match the return type
+               //   expected of this statement.
                struct cond_statement *cs = cast(cond_statement, prog);
                struct casepart *cp;
 
-               t = propagate_types(cs->forpart, c, ok, Tnone, 0);
-               if (!vtype_compat(Tnone, t, 0))
-                       *ok = 0;
-               t = propagate_types(cs->dopart, c, ok, Tnone, 0);
-               if (!vtype_compat(Tnone, t, 0))
-                       *ok = 0;
-               if (cs->dopart) {
-                       t = propagate_types(cs->thenpart, c, ok, Tnone, 0);
-                       if (!vtype_compat(Tnone, t, 0))
-                               *ok = 0;
-               }
-               if (cs->casepart == NULL)
-                       propagate_types(cs->condpart, c, ok, Tbool, 0);
-               else {
+               t = propagate_types(cs->forpart, c, perr, Tnone, 0);
+
+               if (cs->looppart) {
+                       t = propagate_types(cs->thenpart, c, perr, Tnone, 0);
+               }
+               if (cs->casepart == NULL) {
+                       propagate_types(cs->condpart, c, perr, Tbool, 0);
+                       propagate_types(cs->looppart, c, perr, Tbool, 0);
+               } else {
                        /* Condpart must match case values, with bool permitted */
                        t = NULL;
                        for (cp = cs->casepart;
                             cp && !t; cp = cp->next)
-                               t = propagate_types(cp->value, c, ok, NULL, 0);
+                               t = propagate_types(cp->value, c, perr, NULL, 0);
                        if (!t && cs->condpart)
-                               t = propagate_types(cs->condpart, c, ok, NULL, Rboolok);
+                               t = propagate_types(cs->condpart, c, perr, // NOTEST
+                                                   NULL, Rboolok);
+                       if (!t && cs->looppart)
+       
+                       t = propagate_types(cs->looppart, c, perr, NULL, // NOTEST
+                                                   Rboolok);
                        // Now we have a type (I hope) push it down
                        if (t) {
                                for (cp = cs->casepart; cp; cp = cp->next)
-                                       propagate_types(cp->value, c, ok, t, 0);
-                               propagate_types(cs->condpart, c, ok, t, Rboolok);
+                                       propagate_types(cp->value, c, perr, t, 0);
+                               propagate_types(cs->condpart, c, perr, t, Rboolok);
+                               propagate_types(cs->looppart, c, perr, t, Rboolok);
                        }
                }
                // (if)then, else, and case parts must return expected type.
-               if (!cs->dopart && !type)
-                       type = propagate_types(cs->thenpart, c, ok, NULL, rules);
+               if (!cs->looppart && !type)
+                       type = propagate_types(cs->thenpart, c, perr, NULL, rules);
                if (!type)
-                       type = propagate_types(cs->elsepart, c, ok, NULL, rules);
+                       type = propagate_types(cs->elsepart, c, perr, NULL, rules);
                for (cp = cs->casepart;
                     cp && !type;
-                    cp = cp->next)
-                       type = propagate_types(cp->action, c, ok, NULL, rules);
+                    cp = cp->next)     // NOTEST
+                       type = propagate_types(cp->action, c, perr, NULL, rules);       // NOTEST
                if (type) {
-                       if (!cs->dopart)
-                               propagate_types(cs->thenpart, c, ok, type, rules);
-                       propagate_types(cs->elsepart, c, ok, type, rules);
+                       if (!cs->looppart)
+                               propagate_types(cs->thenpart, c, perr, type, rules);
+                       propagate_types(cs->elsepart, c, perr, type, rules);
                        for (cp = cs->casepart; cp ; cp = cp->next)
-                               propagate_types(cp->action, c, ok, type, rules);
+                               propagate_types(cp->action, c, perr, type, rules);
                        return type;
                } else
                        return NULL;
        }
 
+###### interp binode cases
+       case Loop:
+               // This just performs one iterration of the loop
+               rv = interp_exec(c, b->left, &rvtype);
+               if (rvtype == Tnone ||
+                   (rvtype == Tbool && rv.bool != 0))
+                       // rvtype is Tnone or Tbool, doesn't need to be freed
+                       interp_exec(c, b->right, NULL);
+               break;
+
 ###### interp exec cases
        case Xcond_statement:
        {
                struct value v, cnd;
+               struct type *vtype, *cndtype;
                struct casepart *cp;
-               struct cond_statement *c = cast(cond_statement, e);
+               struct cond_statement *cs = cast(cond_statement, e);
 
-               if (c->forpart)
-                       interp_exec(c->forpart);
-               do {
-                       if (c->condpart)
-                               cnd = interp_exec(c->condpart);
-                       else
-                               cnd.type = Tnone;
-                       if (!(cnd.type == Tnone ||
-                             (cnd.type == Tbool && cnd.bool != 0)))
-                               break;
-                       // cnd is Tnone or Tbool, doesn't need to be freed
-                       if (c->dopart)
-                               interp_exec(c->dopart);
-
-                       if (c->thenpart) {
-                               v = interp_exec(c->thenpart);
-                               if (v.type != Tnone || !c->dopart)
-                                       return v;
-                               free_value(v);
+               if (cs->forpart)
+                       interp_exec(c, cs->forpart, NULL);
+               if (cs->looppart) {
+                       while ((cnd = interp_exec(c, cs->looppart, &cndtype)),
+                              cndtype == Tnone || (cndtype == Tbool && cnd.bool != 0))
+                               interp_exec(c, cs->thenpart, NULL);
+               } else {
+                       cnd = interp_exec(c, cs->condpart, &cndtype);
+                       if ((cndtype == Tnone ||
+                           (cndtype == Tbool && cnd.bool != 0))) {
+                               // cnd is Tnone or Tbool, doesn't need to be freed
+                               rv = interp_exec(c, cs->thenpart, &rvtype);
+                               // skip else (and cases)
+                               goto Xcond_done;
                        }
-               } while (c->dopart);
-
-               for (cp = c->casepart; cp; cp = cp->next) {
-                       v = interp_exec(cp->value);
-                       if (value_cmp(v, cnd) == 0) {
-                               free_value(v);
-                               free_value(cnd);
-                               return interp_exec(cp->action);
+               }
+               for (cp = cs->casepart; cp; cp = cp->next) {
+                       v = interp_exec(c, cp->value, &vtype);
+                       if (value_cmp(cndtype, vtype, &v, &cnd) == 0) {
+                               free_value(vtype, &v);
+                               free_value(cndtype, &cnd);
+                               rv = interp_exec(c, cp->action, &rvtype);
+                               goto Xcond_done;
                        }
-                       free_value(v);
+                       free_value(vtype, &v);
                }
-               free_value(cnd);
-               if (c->elsepart)
-                       return interp_exec(c->elsepart);
-               v.type = Tnone;
-               return v;
+               free_value(cndtype, &cnd);
+               if (cs->elsepart)
+                       rv = interp_exec(c, cs->elsepart, &rvtype);
+               else
+                       rvtype = Tnone;
+       Xcond_done:
+               break;
        }
 
-### Finally the whole program.
+### Top level structure
 
-Somewhat reminiscent of Pascal a (current) Ocean program starts with
-the keyword "program" and a list of variable names which are assigned
-values from command line arguments.  Following this is a `block` which
-is the code to execute.
+All the language elements so far can be used in various places.  Now
+it is time to clarify what those places are.
 
-As this is the top level, several things are handled a bit
-differently.
-The whole program is not interpreted by `interp_exec` as that isn't
-passed the argument list which the program requires.  Similarly type
-analysis is a bit more interesting at this level.
+At the top level of a file there will be a number of declarations.
+These can be for predefined constants, `struct` types, and functions -
+particularly the `main` function.
 
-###### Binode types
-       Program,
+The various declarations do not return anything.  They store the
+various declarations in the parse context.
 
 ###### Parser: grammar
 
-       $*binode
-       Program -> InitProgram OpenScope Varlist Block OptNL ${
-               $0 = new(binode);
-               $0->op = Program;
-               $0->left = reorder_bilist($<3);
-               $0->right = $<4;
-               var_block_close(config2context(config), CloseSequential);
-               if (config2context(config)->scope_stack) abort();
-               }$
-               | ERROR ${
-                       tok_err(config2context(config),
-                               "error: unhandled parse error.", &$1);
-               }$
-
-       InitProgram -> program ${
-               context_init(config2context(config));
-               }$
-
-       Varlist -> Varlist ArgDecl ${
-                       $0 = new(binode);
-                       $0->op = Program;
-                       $0->left = $<1;
-                       $0->right = $<2;
-               }$
-               | ${ $0 = NULL; }$
+       $void
+       Ocean -> OptNL DeclarationList
 
-       $*var
-       ArgDecl -> IDENTIFIER ${ {
-               struct variable *v = var_decl(config2context(config), $1.txt);
-               $0 = new(var);
-               $0->var = v;
-       } }$
+       ## declare terminals
+
+       OptNL ->
+       | OptNL NEWLINE
+
+       Newlines -> NEWLINE
+       | Newlines NEWLINE
+
+       DeclarationList -> Declaration
+       | DeclarationList Declaration
+
+       Declaration -> ERROR Newlines ${
+               tok_err(c,      // NOTEST
+                       "error: unhandled parse error", &$1);
+       }$
+       | DeclareConstant
+       | DeclareFunction
+       | DeclareStruct
+
+       ## top level grammar
 
        ## Grammar
 
-###### print binode cases
-       case Program:
-               do_indent(indent, "program");
-               for (b2 = cast(binode, b->left); b2; b2 = cast(binode, b2->right)) {
-                       printf(" ");
-                       print_exec(b2->left, 0, 0);
-               }
-               if (bracket)
-                       printf(" {\n");
-               else
-                       printf(":\n");
-               print_exec(b->right, indent+1, bracket);
-               if (bracket)
-                       do_indent(indent, "}\n");
-               break;
+### The `const` section
 
-###### propagate binode cases
-       case Program: abort();
+As well as being defined in with the code that uses them, constants can
+be declared at the top level.  These have full-file scope, so they are
+always `InScope`, even before(!) they have been declared.  The value of
+a top level constant can be given as an expression, and this is
+evaluated after parsing and before execution.
+
+A function call can syntactically be used to evaluate a constant, but as
+yet we don't detect which functions are safe to use that way, so this
+does not actually work.
+
+Constants are defined in a section that starts with the reserved word
+`const` and then has a block with a list of assignment statements.
+For syntactic consistency, these must use the double-colon syntax to
+make it clear that they are constants.  Type can also be given: if
+not, the type will be determined during analysis, as with other
+constants.
+
+###### parse context
+       struct binode *constlist;
+
+###### top level grammar
+
+       $TERM const
+
+       DeclareConstant -> const { IN OptNL ConstList OUT OptNL } Newlines
+       | const { SimpleConstList } Newlines
+       | const IN OptNL ConstList OUT Newlines
+       | const SimpleConstList Newlines
+
+       ConstList -> ConstList SimpleConstLine
+       | SimpleConstLine
+
+       SimpleConstList -> SimpleConstList ; Const
+       | Const
+       | SimpleConstList ;
+
+       SimpleConstLine -> SimpleConstList Newlines
+       | ERROR Newlines ${ tok_err(c, "Syntax error in constant", &$1); }$
+
+       $*type
+       CType -> Type   ${ $0 = $<1; }$
+       |               ${ $0 = NULL; }$
+
+       $void
+       Const -> IDENTIFIER :: CType = Expression ${ {
+               struct variable *v;
+               struct binode *bl, *bv;
+               struct var *var = new_pos(var, $ID);
+
+               v = var_decl(c, $ID.txt);
+               if (v) {
+                       v->where_decl = var;
+                       v->where_set = var;
+                       v->type = $<CT;
+                       v->constant = 1;
+                       v->global = 1;
+               } else {
+                       v = var_ref(c, $1.txt);
+                       if (v->type == Tnone) {
+                               v->where_decl = var;
+                               v->where_set = var;
+                               v->type = $<CT;
+                               v->constant = 1;
+                               v->global = 1;
+                       } else {
+                               tok_err(c, "error: name already declared", &$1);
+                               type_err(c, "info: this is where '%v' was first declared",
+                                        v->where_decl, NULL, 0, NULL);
+                       }
+               }
+               var->var = v;
+
+               bv = new(binode);
+               bv->op = Declare;
+               bv->left = var;
+               bv->right= $<Exp;
+
+               bl = new(binode);
+               bl->op = List;
+               bl->left = c->constlist;
+               bl->right = bv;
+               c->constlist = bl;
+       } }$
 
 ###### core functions
+       static void resolve_consts(struct parse_context *c)
+       {
+               struct binode *b;
+               int retry = 1;
+               enum { none, some, cannot } progress = none;
+
+               c->constlist = reorder_bilist(c->constlist);
+               while (retry) {
+                       retry = 0;
+                       for (b = cast(binode, c->constlist); b;
+                            b = cast(binode, b->right)) {
+                               enum prop_err perr;
+                               struct binode *vb = cast(binode, b->left);
+                               struct var *v = cast(var, vb->left);
+                               if (v->var->frame_pos >= 0)
+                                       continue;
+                               do {
+                                       perr = 0;
+                                       propagate_types(vb->right, c, &perr,
+                                                       v->var->type, 0);
+                               } while (perr & Eretry);
+                               if (perr & Efail)
+                                       c->parse_error += 1;
+                               else if (!(perr & Eruntime)) {
+                                       progress = some;
+                                       struct value res = interp_exec(
+                                               c, vb->right, &v->var->type);
+                                       global_alloc(c, v->var->type, v->var, &res);
+                               } else {
+                                       if (progress == cannot)
+                                               type_err(c, "error: const %v cannot be resolved.",
+                                                        v, NULL, 0, NULL);
+                                       else
+                                               retry = 1;
+                               }
+                       }
+                       switch (progress) {
+                       case cannot:
+                               retry = 0; break;
+                       case none:
+                               progress = cannot; break;
+                       case some:
+                               progress = none; break;
+                       }
+               }
+       }
 
-       static int analyse_prog(struct exec *prog, struct parse_context *c)
+###### print const decls
        {
-               struct binode *b = cast(binode, prog);
-               int ok = 1;
+               struct binode *b;
+               int first = 1;
+
+               for (b = cast(binode, context.constlist); b;
+                    b = cast(binode, b->right)) {
+                       struct binode *vb = cast(binode, b->left);
+                       struct var *vr = cast(var, vb->left);
+                       struct variable *v = vr->var;
+
+                       if (first)
+                               printf("const\n");
+                       first = 0;
+
+                       printf("    %.*s :: ", v->name->name.len, v->name->name.txt);
+                       type_print(v->type, stdout);
+                       printf(" = ");
+                       print_exec(vb->right, -1, 0);
+                       printf("\n");
+               }
+       }
 
-               if (!b)
-                       return 0;
-               do {
-                       ok = 1;
-                       propagate_types(b->right, c, &ok, Tnone, 0);
-               } while (ok == 2);
-               if (!ok)
-                       return 0;
+###### free const decls
+       free_binode(context.constlist);
+
+### Function declarations
+
+The code in an Ocean program is all stored in function declarations.
+One of the functions must be named `main` and it must accept an array of
+strings as a parameter - the command line arguments.
+
+As this is the top level, several things are handled a bit differently.
+The function is not interpreted by `interp_exec` as that isn't passed
+the argument list which the program requires.  Similarly type analysis
+is a bit more interesting at this level.
+
+###### ast functions
 
-               for (b = cast(binode, b->left); b; b = cast(binode, b->right)) {
+       static struct type *handle_results(struct parse_context *c,
+                                          struct binode *results)
+       {
+               /* Create a 'struct' type from the results list, which
+                * is a list for 'struct var'
+                */
+               struct type *t = add_anon_type(c, &structure_prototype,
+                                              "function result");
+               int cnt = 0;
+               struct binode *b;
+
+               for (b = results; b; b = cast(binode, b->right))
+                       cnt += 1;
+               t->structure.nfields = cnt;
+               t->structure.fields = calloc(cnt, sizeof(struct field));
+               cnt = 0;
+               for (b = results; b; b = cast(binode, b->right)) {
                        struct var *v = cast(var, b->left);
-                       if (!v->var->val.type) {
-                               v->var->where_set = b;
-                               v->var->val = val_init(Tstr);
+                       struct field *f = &t->structure.fields[cnt++];
+                       int a = v->var->type->align;
+                       f->name = v->var->name->name;
+                       f->type = v->var->type;
+                       f->init = NULL;
+                       f->offset = t->size;
+                       v->var->frame_pos = f->offset;
+                       t->size += ((f->type->size - 1) | (a-1)) + 1;
+                       if (a > t->align)
+                               t->align = a;
+                       variable_unlink_exec(v->var);
+               }
+               free_binode(results);
+               return t;
+       }
+
+       static struct variable *declare_function(struct parse_context *c,
+                                               struct variable *name,
+                                               struct binode *args,
+                                               struct type *ret,
+                                               struct binode *results,
+                                               struct exec *code)
+       {
+               if (name) {
+                       struct value fn = {.function = code};
+                       struct type *t;
+                       var_block_close(c, CloseFunction, code);
+                       t = add_anon_type(c, &function_prototype,
+                                         "func %.*s", name->name->name.len,
+                                         name->name->name.txt);
+                       name->type = t;
+                       t->function.params = reorder_bilist(args);
+                       if (!ret) {
+                               ret = handle_results(c, reorder_bilist(results));
+                               t->function.inline_result = 1;
+                               t->function.local_size = ret->size;
+                       }
+                       t->function.return_type = ret;
+                       global_alloc(c, t, name, &fn);
+                       name->type->function.scope = c->out_scope;
+               } else {
+                       free_binode(args);
+                       free_type(ret);
+                       free_exec(code);
+                       var_block_close(c, CloseFunction, NULL);
+               }
+               c->out_scope = NULL;
+               return name;
+       }
+
+###### declare terminals
+       $TERM return
+
+###### top level grammar
+
+       $*variable
+       DeclareFunction -> func FuncName ( OpenScope ArgsLine ) Block Newlines ${
+               $0 = declare_function(c, $<FN, $<Ar, Tnone, NULL, $<Bl);
+       }$
+       | func FuncName IN OpenScope Args OUT OptNL do Block Newlines ${
+               $0 = declare_function(c, $<FN, $<Ar, Tnone, NULL, $<Bl);
+       }$
+       | func FuncName NEWLINE OpenScope OptNL do Block Newlines ${
+               $0 = declare_function(c, $<FN, NULL, Tnone, NULL, $<Bl);
+       }$
+       | func FuncName ( OpenScope ArgsLine ) : Type Block Newlines ${
+               $0 = declare_function(c, $<FN, $<Ar, $<Ty, NULL, $<Bl);
+       }$
+       | func FuncName ( OpenScope ArgsLine ) : ( ArgsLine ) Block Newlines ${
+               $0 = declare_function(c, $<FN, $<AL, NULL, $<AL2, $<Bl);
+       }$
+       | func FuncName IN OpenScope Args OUT OptNL return Type Newlines do Block Newlines ${
+               $0 = declare_function(c, $<FN, $<Ar, $<Ty, NULL, $<Bl);
+       }$
+       | func FuncName NEWLINE OpenScope return Type Newlines do Block Newlines ${
+               $0 = declare_function(c, $<FN, NULL, $<Ty, NULL, $<Bl);
+       }$
+       | func FuncName IN OpenScope Args OUT OptNL return IN Args OUT OptNL do Block Newlines ${
+               $0 = declare_function(c, $<FN, $<Ar, NULL, $<Ar2, $<Bl);
+       }$
+       | func FuncName NEWLINE OpenScope return IN Args OUT OptNL do Block Newlines ${
+               $0 = declare_function(c, $<FN, NULL, NULL, $<Ar, $<Bl);
+       }$
+
+###### print func decls
+       {
+               struct variable *v;
+               int target = -1;
+
+               while (target != 0) {
+                       int i = 0;
+                       for (v = context.in_scope; v; v=v->in_scope)
+                               if (v->depth == 0 && v->type && v->type->check_args) {
+                                       i += 1;
+                                       if (i == target)
+                                               break;
+                               }
+
+                       if (target == -1) {
+                               target = i;
+                       } else {
+                               struct value *val = var_value(&context, v);
+                               printf("func %.*s", v->name->name.len, v->name->name.txt);
+                               v->type->print_type_decl(v->type, stdout);
+                               if (brackets) {
+                                       printf(" {\n");
+                                       print_exec(val->function, 1, brackets);
+                                       printf("}\n");
+                               } else {
+                                       print_value(v->type, val, stdout);
+                               }
+                               printf("/* frame size %d */\n", v->type->function.local_size);
+                               target -= 1;
                        }
                }
-               b = cast(binode, prog);
-               do {
-                       ok = 1;
-                       propagate_types(b->right, c, &ok, Tnone, 0);
-               } while (ok == 2);
-               if (!ok)
-                       return 0;
+       }
+
+###### core functions
+
+       static int analyse_funcs(struct parse_context *c)
+       {
+               struct variable *v;
+               int all_ok = 1;
+               for (v = c->in_scope; v; v = v->in_scope) {
+                       struct value *val;
+                       struct type *ret;
+                       enum prop_err perr;
+                       if (v->depth != 0 || !v->type || !v->type->check_args)
+                               continue;
+                       ret = v->type->function.inline_result ?
+                               Tnone : v->type->function.return_type;
+                       val = var_value(c, v);
+                       do {
+                               perr = 0;
+                               propagate_types(val->function, c, &perr, ret, 0);
+                       } while (!(perr & Efail) && (perr & Eretry));
+                       if (!(perr & Efail))
+                               /* Make sure everything is still consistent */
+                               propagate_types(val->function, c, &perr, ret, 0);
+                       if (perr & Efail)
+                               all_ok = 0;
+                       if (!v->type->function.inline_result &&
+                           !v->type->function.return_type->dup) {
+                               type_err(c, "error: function cannot return value of type %1",
+                                        v->where_decl, v->type->function.return_type, 0, NULL);
+                       }
 
-               /* Make sure everything is still consistent */
-               propagate_types(b->right, c, &ok, Tnone, 0);
-               return !!ok;
+                       scope_finalize(c, v->type);
+               }
+               return all_ok;
+       }
+
+       static int analyse_main(struct type *type, struct parse_context *c)
+       {
+               struct binode *bp = type->function.params;
+               struct binode *b;
+               enum prop_err perr;
+               int arg = 0;
+               struct type *argv_type;
+
+               argv_type = add_anon_type(c, &array_prototype, "argv");
+               argv_type->array.member = Tstr;
+               argv_type->array.unspec = 1;
+
+               for (b = bp; b; b = cast(binode, b->right)) {
+                       perr = 0;
+                       switch (arg++) {
+                       case 0: /* argv */
+                               propagate_types(b->left, c, &perr, argv_type, 0);
+                               break;
+                       default: /* invalid */  // NOTEST
+                               propagate_types(b->left, c, &perr, Tnone, 0);   // NOTEST
+                       }
+                       if (perr & Efail)
+                               c->parse_error += 1;
+               }
+
+               return !c->parse_error;
        }
 
-       static void interp_prog(struct exec *prog, char **argv)
+       static void interp_main(struct parse_context *c, int argc, char **argv)
        {
-               struct binode *p = cast(binode, prog);
+               struct value *progp = NULL;
+               struct text main_name = { "main", 4 };
+               struct variable *mainv;
                struct binode *al;
+               int anum = 0;
                struct value v;
-
-               if (!prog)
+               struct type *vtype;
+
+               mainv = var_ref(c, main_name);
+               if (mainv)
+                       progp = var_value(c, mainv);
+               if (!progp || !progp->function) {
+                       fprintf(stderr, "oceani: no main function found.\n");
+                       c->parse_error += 1;
+                       return;
+               }
+               if (!analyse_main(mainv->type, c)) {
+                       fprintf(stderr, "oceani: main has wrong type.\n");
+                       c->parse_error += 1;
                        return;
-               al = cast(binode, p->left);
+               }
+               al = mainv->type->function.params;
+
+               c->local_size = mainv->type->function.local_size;
+               c->local = calloc(1, c->local_size);
                while (al) {
                        struct var *v = cast(var, al->left);
-                       struct value *vl = &v->var->val;
-
-                       if (argv[0] == NULL) {
-                               printf("Not enough args\n");
-                               exit(1);
+                       struct value *vl = var_value(c, v->var);
+                       struct value arg;
+                       struct type *t;
+                       int i;
+
+                       switch (anum++) {
+                       case 0: /* argv */
+                               t = v->var->type;
+                               t->array.size = argc;
+                               t->prepare_type(c, t, 0);
+                               array_init(v->var->type, vl);
+                               for (i = 0; i < argc; i++) {
+                                       struct value *vl2 = vl->array + i * v->var->type->array.member->size;
+
+                                       arg.str.txt = argv[i];
+                                       arg.str.len = strlen(argv[i]);
+                                       free_value(Tstr, vl2);
+                                       dup_value(Tstr, &arg, vl2);
+                               }
+                               break;
                        }
                        al = cast(binode, al->right);
-                       free_value(*vl);
-                       *vl = parse_value(vl->type, argv[0]);
-                       if (vl->type == NULL)
-                               exit(1);
-                       argv++;
                }
-               v = interp_exec(p->right);
-               free_value(v);
+               v = interp_exec(c, progp->function, &vtype);
+               free_value(vtype, &v);
+               free(c->local);
+               c->local = NULL;
        }
 
-###### interp binode cases
-       case Program: abort();
-
 ## And now to test it out.
 
-Having a language requires having a "hello world" program. I'll
+Having a language requires having a "hello world" program.  I'll
 provide a little more than that: a program that prints "Hello world"
 finds the GCD of two numbers, prints the first few elements of
-Fibonacci, and performs a binary search for a number.
+Fibonacci, performs a binary search for a number, and a few other
+things which will likely grow as the languages grows.
 
 ###### File: oceani.mk
-       tests :: sayhello
+       demos :: sayhello
        sayhello : oceani
-               @echo "===== TEST ====="
-               ./oceani --section "test: hello" oceani.mdc 55 33
+               @echo "===== DEMO ====="
+               ./oceani --section "demo: hello" oceani.mdc 55 33
+
+###### demo: hello
+
+       const
+               pi ::= 3.141_592_6
+               four ::= 2 + 2 ; five ::= 10/2
+       const pie ::= "I like Pie";
+               cake ::= "The cake is"
+                 ++ " a lie"
+
+       struct fred
+               size:[four]number
+               name:string
+               alive:Boolean
 
-###### test: hello
+       func fibonacci(n:number):number
+               if n <= 2:
+                       use 1
+               else use fibonacci(n-1) + fibonacci(n-2)
 
-       program A B:
+       func main(argv:[]string)
                print "Hello World, what lovely oceans you have!"
+               print "Are there", five, "?"
+               print pi, pie, "but", cake
+
+               A := $argv[1]; B := $argv[2]
+
                /* When a variable is defined in both branches of an 'if',
                 * and used afterwards, the variables are merged.
                 */
                if A > B:
                        bigger := "yes"
-               else:
+               else
                        bigger := "no"
                print "Is", A, "bigger than", B,"? ", bigger
                /* If a variable is not used after the 'if', no
                 * merge happens, so types can be different
                 */
-               if A * 2 > B:
+               if A > B * 2:
                        double:string = "yes"
                        print A, "is more than twice", B, "?", double
-               else:
-                       double := A*2
-                       print "double", A, "is only", double
+               else
+                       double := B*2
+                       print "double", B, "is", double
 
-               a := A;
+               a : number
+               a = A;
                b:number = B
                if a > 0 and b > 0:
                        while a != b:
                                if a < b:
                                        b = b - a
-                               else:
+                               else
                                        a = a - b
                        print "GCD of", A, "and", B,"is", a
                else if a <= 0:
                        print a, "is not positive, cannot calculate GCD"
-               else:
+               else
                        print b, "is not positive, cannot calculate GCD"
 
-               for:
+               for
                        togo := 10
                        f1 := 1; f2 := 1
                        print "Fibonacci:", f1,f2,
@@ -3231,24 +6045,64 @@ Fibonacci, and performs a binary search for a number.
                        f1 = f2
                        f2 = f3
                print ""
+               for
+                       f := 1
+                       print "Fibonacci:",
+               then f = f + 1
+               while f < 13:
+                       print "", fibonacci(f),
+               print
 
                /* Binary search... */
-               for:
+               for
                        lo:= 0; hi := 100
                        target := 77
-               while:
+               while
                        mid := (lo + hi) / 2
                        if mid == target:
-                               use Found
+                               use .Found
                        if mid < target:
                                lo = mid
-                       else:
+                       else
                                hi = mid
                        if hi - lo < 1:
-                               use GiveUp
+                               lo = mid
+                               use .GiveUp
                        use True
-               do: pass
-               case Found:
+               do pass
+               case .Found:
                        print "Yay, I found", target
-               case GiveUp:
-                       print "Closest I found was", mid
+               case .GiveUp:
+                       print "Closest I found was", lo
+
+               size::= 10
+               list:[size]number
+               list[0] = 1234
+               // "middle square" PRNG.  Not particularly good, but one my
+               // Dad taught me - the first one I ever heard of.
+               for i:=1; then i = i + 1; while i < size:
+                       n := list[i-1] * list[i-1]
+                       list[i] = (n / 100) % 10 000
+
+               print "Before sort:",
+               for i:=0; then i = i + 1; while i < size:
+                       print "", list[i],
+               print
+
+               for i := 1; then i=i+1; while i < size:
+                       for j:=i-1; then j=j-1; while j >= 0:
+                               if list[j] > list[j+1]:
+                                       t:= list[j]
+                                       list[j] = list[j+1]
+                                       list[j+1] = t
+               print " After sort:",
+               for i:=0; then i = i + 1; while i < size:
+                       print "", list[i],
+               print
+
+               if 1 == 2 then print "yes"; else print "no"
+
+               bob:fred
+               bob.name = "Hello"
+               bob.alive = (bob.name == "Hello")
+               print "bob", "is" if  bob.alive else "isn't", "alive"