]> ocean-lang.org Git - ocean/blobdiff - csrc/oceani.mdc
Oceani - Cataract Creek version
[ocean] / csrc / oceani.mdc
index 178532c07a9c1a17d6c2baf788c381fbd533cc9d..98430c21e93b1951f7ce7a80f26338c2ab7a82ca 100644 (file)
@@ -1,4 +1,4 @@
-# Ocean Interpreter - Jamison Creek version
+# Ocean Interpreter - Cataract Creek version
 
 Ocean is intended to be a compiled language, so this interpreter is
 not targeted at being the final product.  It is, rather, an intermediate
@@ -29,40 +29,55 @@ be.
 
 ## Current version
 
-This third version of the interpreter exists to test out some initial
-ideas relating to types.  Particularly it adds arrays (indexed from
-zero) and simple structures.  Basic control flow and variable scoping
-are already fairly well established, as are basic numerical and
-boolean operators.
-
-Some operators that have only recently been added, and so have not
-generated all that much experience yet are "and then" and "or else" as
-short-circuit Boolean operators, and the "if ... else" trinary
-operator which can select between two expressions based on a third
-(which appears syntactically in the middle).
-
-The "func" clause currently only allows a "main" function to be
-declared.  That will be extended when proper function support is added.
-
-An element that is present purely to make a usable language, and
-without any expectation that they will remain, is the "print" statement
-which performs simple output.
-
-The current scalar types are "number", "Boolean", and "string".
-Boolean will likely stay in its current form, the other two might, but
-could just as easily be changed.
+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 Jamison
+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
 
@@ -72,24 +87,33 @@ 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,
+- 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.
 
-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 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.
 
+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
 
        myCFLAGS := -Wall -g -fplan9-extensions
@@ -110,6 +134,7 @@ structures can be used.
        ## macros
        struct parse_context;
        ## ast
+       ## ast late
        struct parse_context {
                struct token_config config;
                char *file_name;
@@ -166,6 +191,9 @@ structures can be used.
        };
        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
@@ -180,10 +208,7 @@ structures can be used.
                char *section = NULL;
                struct parse_context context = {
                        .config = {
-                               .ignored = (1 << TK_mark),
-                               .number_chars = ".,_+- ",
-                               .word_start = "_",
-                               .word_cont = "_",
+                               ## scanner configuration
                        },
                };
                int doprint=0, dotrace=0, doexec=1, brackets=0;
@@ -273,11 +298,25 @@ structures can be used.
                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
 
-The 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.
@@ -285,17 +324,21 @@ a little explanation is the analysis step.
 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.
+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 referenced after the
 conditional statement.  When this happens, the types must naturally be
@@ -303,26 +346,32 @@ 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.
 
-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 expected
-type that gets passed around comprises a type and a flag to indicate
-that `Tbool` 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 understanding of the sort
-of error message that are useful will help guide the process of
+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
@@ -336,44 +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
-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. e 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 might name a type that has not
-been defined.  For these we have `tok_err()` which reports an error
-with a given token.  Each of the error functions sets the flag in the
-context so indicate that parsing failed.
+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, int rules, struct type *t2);
+                            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);
@@ -406,15 +458,14 @@ context so indicate that parsing failed.
                c->parse_error += 1;
        }
 
-## Entities: declared and predeclared.
+## Entities: values, types, variables, and code.
 
-There are various "things" that the language and/or the interpreter
-needs to know about to parse and execute a program.  These include
-types, variables, values, and executable code.  These are all lumped
-together under the term "entities" (calling them "objects" would be
-confusing) and introduced here.  The following section will present the
-different specific code elements which comprise or manipulate these
-various entities.
+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.
 
 ### Executables
 
@@ -423,28 +474,39 @@ 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.  The first field in any `struct exec` is the type
-from the `exec_types` enum.
+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.
 
 ###### macros
        #define cast(structname, pointer) ({            \
-               const typeof( ((struct structname *)0)->type) *__mptr = &(pointer)->type; \
+               const typeof( ((struct structname *)0)->type) *__mptr =         \
+                                                        &(pointer)->type;      \
                if (__mptr && *__mptr != X##structname) abort();                \
                (struct structname *)( (char *)__mptr);})
 
        #define new(structname) ({                                              \
-               struct structname *__ptr = ((struct structname *)calloc(1,sizeof(struct structname))); \
-               __ptr->type = X##structname;                                            \
-               __ptr->line = -1; __ptr->column = -1;                                   \
+               struct structname *__ptr = ((struct structname *)calloc(        \
+                                               1,sizeof(struct structname)));  \
+               __ptr->type = X##structname;                                    \
+               __ptr->line = -1; __ptr->column = -1;                           \
                __ptr;})
 
-       #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;                    \
+       #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
@@ -460,6 +522,7 @@ from the `exec_types` enum.
        struct binode {
                struct exec;
                enum Btype {
+                       List,
                        ## Binode types
                } op;
                struct exec *left, *right;
@@ -477,13 +540,33 @@ from the `exec_types` enum.
                }
                if (loc->type == Xbinode)
                        return __fput_loc(cast(binode,loc)->left, f) ||
-                              __fput_loc(cast(binode,loc)->right, f);  // NOTEST
-               return 0;       // NOTEST
+                              __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
+                       fprintf(f, "??:??: ");                  // NOTEST
+       }
+
+       // 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 binode *rv = NULL;
+
+               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;
        }
 
 Each different type of `exec` node needs a number of functions defined,
@@ -530,7 +613,9 @@ and we need `free_binode`.
 
 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.
+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
 
@@ -546,6 +631,7 @@ also want to know what sort of bracketing to use.
        {
                struct binode *b2;
                switch(b->op) {
+               case List: abort(); // must be handled by parent NOTEST
                ## print binode cases
                }
        }
@@ -553,24 +639,13 @@ also want to know what sort of bracketing to use.
        static void print_exec(struct exec *e, int indent, int bracket)
        {
                if (!e)
-                       return;
+                       return; // NOTEST
                switch (e->type) {
                case Xbinode:
                        print_binode(cast(binode, e), indent, bracket); break;
                ## print exec cases
                }
-               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");
-               }
+               ## print exec extras
        }
 
 ###### forward decls
@@ -588,30 +663,33 @@ 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 immediately, `Enoconst` is set.
+any expression cannot be evaluated a compile time, `Eruntime` is set.
 If the expression can be copied, `Emaycopy` is set.
 
-If it remains unchanged at `0`, then no more propagation is needed.
+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.
 
-###### ast
+Various propagate cases can pass "perr_local" to analyse components of
+an expression which do not affect the result type of the whole
+expression.
 
-       enum val_rules {Rnolabel = 1<<0, Rboolok = 1<<1, Rnoconstant = 1<<2};
-       enum prop_err {Efail = 1<<0, Eretry = 1<<1, Enoconst = 1<<2,
-                      Emaycopy = 1<<3};
+###### ast
 
-###### format cases
-       case 'r':
-               if (rules & Rnolabel)
-                       fputs(" (labels not permitted)", stderr);
-               break;
+       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};
 
 ###### forward decls
-       static struct type *propagate_types(struct exec *prog, struct parse_context *c, enum prop_err *perr,
-                                           struct type *type, int rules);
+       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
 
-       static struct type *__propagate_types(struct exec *prog, struct parse_context *c, enum prop_err *perr,
-                                             struct type *type, int rules)
+       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)
        {
                struct type *t;
 
@@ -623,6 +701,7 @@ If it remains unchanged at `0`, then no more propagation is needed.
                {
                        struct binode *b = cast(binode, prog);
                        switch (b->op) {
+                       case List: abort(); // NOTEST
                        ## propagate binode cases
                        }
                        break;
@@ -632,12 +711,18 @@ If it remains unchanged at `0`, then no more propagation is needed.
                return Tnone;
        }
 
-       static struct type *propagate_types(struct exec *prog, struct parse_context *c, enum prop_err *perr,
-                                           struct type *type, int rules)
+       static struct type *propagate_types(struct exec *prog,
+                                           struct parse_context *c,
+                                           enum prop_err *perr,
+                                           struct type *type,
+                                           enum val_rules rules)
        {
                int pre_err = c->parse_error;
-               struct type *ret = __propagate_types(prog, c, perr, type, rules);
+               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;
@@ -645,28 +730,45 @@ If it remains unchanged at `0`, then no more propagation is needed.
 
 #### Interpreting
 
-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 `main` function
-which needs to look at command line arguments.  This function will be
-interpreted separately.
+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.
+
+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`.
+
+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`.
 
-Each `exec` 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`.
+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);
-###### core functions
+###### 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.
         */
@@ -740,6 +842,7 @@ in `rval`.
                        struct type *ltype, *rtype;
                        ltype = rtype = Tnone;
                        switch (b->op) {
+                       case List: abort();     // NOTEST
                        ## interp binode cases
                        }
                        free_value(ltype, &left);
@@ -761,17 +864,10 @@ in `rval`.
 
 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) 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.
-
-Rather than requiring every numeric type to support all numeric
-operations (add, multiply, etc), we allow types to be able to present
-as one of a few standard types: integer, float, and fraction.  The
-existence of these conversion functions eventually enable types to
-determine if they are compatible with other types, though such types
-have not yet been implemented.
+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.
@@ -780,15 +876,17 @@ 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.
 
-###### ast
-
-       struct value {
-               union {
-                       char ptr[1];
-                       ## value union fields
-               };
-       };
-
+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;
@@ -796,20 +894,19 @@ which might be reported in error messages.
                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);
+               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);
+               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);
-               long long (*to_int)(struct value *v);
-               double (*to_float)(struct value *v);
-               int (*to_mpq)(mpq_t *q, struct value *v);
                ## type functions
                union {
                        ## type union fields
@@ -990,7 +1087,7 @@ which might be reported in error messages.
 ###### forward decls
 
        static void free_value(struct type *type, struct value *v);
-       static int type_compat(struct type *require, struct type *have, int rules);
+       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,
@@ -1047,29 +1144,25 @@ 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,
-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 encoding these cases will simplify some code later.
+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);
+       int (*compat)(struct type *this, struct type *other, enum val_rules rules);
 
 ###### ast functions
 
-       static int type_compat(struct type *require, struct type *have, int rules)
+       static int type_compat(struct type *require, struct type *have,
+                              enum val_rules rules)
        {
-               if ((rules & Rboolok) && have == Tbool)
-                       return 1;       // NOTEST
-               if ((rules & Rnolabel) && have == Tlabel)
-                       return 0;       // NOTEST
                if (!require || !have)
                        return 1;
 
                if (require->compat)
-                       return require->compat(require, have);
+                       return require->compat(require, have, rules);
 
                return require == have;
        }
@@ -1090,7 +1183,7 @@ A separate function encoding these cases will simplify some code later.
        struct text str;
        mpq_t num;
        unsigned char bool;
-       void *label;
+       int label;
 
 ###### ast functions
        static void _free_value(struct type *type, struct value *v)
@@ -1123,8 +1216,8 @@ A separate function encoding these cases will simplify some code later.
                        val->bool = 0;
                        break;
                case Vlabel:
-                       val->label = NULL;
-                       break;
+                       val->label = 0; // NOTEST
+                       break;          // NOTEST
                }
        }
 
@@ -1135,8 +1228,8 @@ A separate function encoding these cases will simplify some code later.
                case Vnone:             // NOTEST
                        break;          // NOTEST
                case Vlabel:
-                       vnew->label = vold->label;
-                       break;
+                       vnew->label = vold->label;      // NOTEST
+                       break;          // NOTEST
                case Vbool:
                        vnew->bool = vold->bool;
                        break;
@@ -1157,7 +1250,7 @@ A separate function encoding these cases will simplify some code later.
        {
                int cmp;
                if (tl != tr)
-                       return tl - tr; // NOTEST
+                       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;
@@ -1174,7 +1267,7 @@ A separate function encoding these cases will simplify some code later.
                case Vnone:                             // NOTEST
                        fprintf(f, "*no-value*"); break;        // NOTEST
                case Vlabel:                            // NOTEST
-                       fprintf(f, "*label-%p*", v->label); break; // NOTEST
+                       fprintf(f, "*label-%d*", v->label); break; // NOTEST
                case Vstr:
                        fprintf(f, "%.*s", v->str.len, v->str.txt); break;
                case Vbool:
@@ -1259,10 +1352,11 @@ executable.
                return v;
        }
 
-###### Grammar
-
+###### declare terminals
        $TERM True False
 
+###### Grammar
+
        $*val
        Value ->  True ${
                $0 = new_val(Tbool, $1);
@@ -1273,13 +1367,13 @@ executable.
                $0->val.bool = 0;
        }$
        | NUMBER ${ {
-               char tail[3];
+               char tail[3] = "";
                $0 = new_val(Tnum, $1);
-               if (number_parse($0->val.num, tail, $1.txt) == 0)
-                       mpq_init($0->val.num);  // UNTESTED
-                       if (tail[0])
-                               tok_err(c, "error: unsupported number suffix",
-                                       &$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];
@@ -1316,8 +1410,9 @@ executable.
        {
                struct val *val = cast(val, prog);
                if (!type_compat(type, val->vtype, rules))
-                       type_err(c, "error: expected %1%r found %2",
+                       type_err(c, "error: expected %1 found %2",
                                   prog, type, rules, val->vtype);
+               *perr |= Erval;
                return val->vtype;
        }
 
@@ -1338,25 +1433,96 @@ executable.
 ###### 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
-       // 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)
+       static int label_lookup(struct parse_context *c, struct text name)
        {
-               struct binode *rv = NULL;
+               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;
+       }
 
-               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;
+###### 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
@@ -1419,9 +1585,20 @@ cannot nest, so a declaration while a name is in-scope is an error.
                ## 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.
+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;
@@ -1439,6 +1616,20 @@ need to know which variables need to be freed when it completes.
                }
        }
 
+###### 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)
        {
@@ -1785,7 +1976,7 @@ all pending-scope variables become conditionally scoped.
                        vp = &(*vp)->in_scope;
                v->in_scope = *vp;
                *vp = v;
-               return 0;               
+               return 0;
        }
 
        static void var_block_close(struct parse_context *c, enum closetype ct,
@@ -1817,7 +2008,9 @@ all pending-scope variables become conditionally scoped.
                        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 */
+                               /* This variable gets cleaned up when
+                                * 'e' finishes
+                                */
                                variable_unlink_exec(v);
                                v->cleanup_exec = e;
                                v->next_free = e->to_free;
@@ -1836,14 +2029,12 @@ all pending-scope variables become conditionally scoped.
                                                 v->previous->scope == PendingScope)
                                                /* all previous branches used name */
                                                v->scope = PendingScope;
-                                       else if (v->type == Tlabel)
-                                               /* Labels remain pending even when not used */
-                                               v->scope = PendingScope;        // UNTESTED
                                        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)
@@ -1865,12 +2056,11 @@ all pending-scope variables become conditionally scoped.
                                break;
                        case CloseFunction:
                                if (v->scope == CondScope)
-                                       /* Condition cannot continue past end of function */
+                                       /* Condition cannot continue past end of
+                                        * function */
                                        v->scope = InScope;
                                /* fallthrough */
                        case CloseSequential:
-                               if (v->type == Tlabel)
-                                       v->scope = PendingScope;
                                switch (v->scope) {
                                case InScope:
                                        v->scope = OutScope;
@@ -1884,10 +2074,7 @@ all pending-scope variables become conditionally scoped.
                                        for (v2 = v;
                                             v2 && v2->scope == PendingScope;
                                             v2 = v2->previous)
-                                               if (v2->type == Tlabel)
-                                                       v2->scope = CondScope;
-                                               else
-                                                       v2->scope = OutScope;
+                                               v2->scope = OutScope;
                                        break;
                                case CondScope:
                                case OutScope: break;
@@ -1899,7 +2086,7 @@ all pending-scope variables become conditionally scoped.
 
 #### Storing Values
 
-The value of a variable is store separately from the variable, on an
+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
@@ -1910,7 +2097,7 @@ 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.
 
-We initialize the `frame_pos` to an impossible value, so that we can
+We initialise the `frame_pos` to an impossible value, so that we can
 tell if it was set or not later.
 
 ###### variable fields
@@ -1936,7 +2123,7 @@ tell if it was set or not later.
        {
                if (!v->global) {
                        if (!c->local || !v->type)
-                               return NULL;    // UNTESTED
+                               return NULL;    // NOTEST
                        if (v->frame_pos + v->type->size > c->local_size) {
                                printf("INVALID frame_pos\n");  // NOTEST
                                exit(2);                        // NOTEST
@@ -1962,7 +2149,7 @@ tell if it was set or not later.
                        t->prepare_type(c, t, 1);       // NOTEST
 
                if (c->global_size & (t->align - 1))
-                       c->global_size = (c->global_size + t->align) & ~(t->align-1);   // NOTEST
+                       c->global_size = (c->global_size + t->align) & ~(t->align-1);
                if (!v) {
                        v = &scratch;
                        v->type = t;
@@ -1974,7 +2161,7 @@ tell if it was set or not later.
                if (init)
                        memcpy(ret, init, t->size);
                else
-                       val_init(t, ret);
+                       val_init(t, ret);       // NOTEST
                return ret;
        }
 
@@ -2009,7 +2196,7 @@ stack.
                        if (v->merged != v)
                                continue;
                        if (!t)
-                               continue;
+                               continue;       // NOTEST
                        if (v->frame_pos >= 0)
                                continue;
                        while (done && done->scope_end < v->scope_start)
@@ -2187,13 +2374,6 @@ correctly.
                        return Tnone;                                   // NOTEST
                }
                v = v->merged;
-               if (v->constant && (rules & Rnoconstant)) {
-                       type_err(c, "error: Cannot assign to a constant: %v",
-                                prog, NULL, 0, NULL);
-                       type_err(c, "info: name was defined as a constant here",
-                                v->where_decl, NULL, 0, NULL);
-                       return v->type;
-               }
                if (v->type == Tnone && v->where_decl == prog)
                        type_err(c, "error: variable used but not declared: %v",
                                 prog, NULL, 0, NULL);
@@ -2204,16 +2384,16 @@ correctly.
                                *perr |= Eretry;
                        }
                } else if (!type_compat(type, v->type, rules)) {
-                       type_err(c, "error: expected %1%r but variable '%v' is %2", prog,
+                       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 |= Enoconst;
-               if (!type)
-                       return v->type;
-               return type;
+                       *perr |= Eruntime;
+               if (v->constant)
+                       *perr |= Econst;
+               return v->type;
        }
 
 ###### interp exec cases
@@ -2238,7 +2418,6 @@ correctly.
 ###### 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
@@ -2248,22 +2427,44 @@ 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 "Expression".  Where a Term is formed by some operation on another
+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 each terms of each type, we will start out by
+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
 
-Thus far the complex types we have are arrays and structs.
+       $*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
 
@@ -2272,18 +2473,18 @@ Arrays can be declared by giving a size and a type, as `[size]type' so
 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 a
-new variable as the size: `name:[size::number]string`.  The `size`
-variable is set to the size of the array and must be a constant.  As
-`number` is the only supported type, it can be left out:
-`name:[size::]string`.
+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 pointers are introduced we will also
-introduce 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 define which can be used to
-make a copy of an array with controllable recursive depth.
+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
@@ -2318,16 +2519,16 @@ with a const size by whether they are prepared at parse time or not.
                struct value *vsize;
                mpz_t q;
                if (type->array.static_size)
-                       return 1;       // UNTESTED
+                       return 1;       // NOTEST - guard against reentry
                if (type->array.unspec && parse_time)
-                       return 1;       // UNTESTED
+                       return 1;       // NOTEST - unspec is still incomplete
                if (parse_time && type->array.vsize && !type->array.vsize->global)
-                       return 1;       // UNTESTED
+                       return 1;       // NOTEST - should be impossible
 
                if (type->array.vsize) {
                        vsize = var_value(c, type->array.vsize);
                        if (!vsize)
-                               return 1;       // UNTESTED
+                               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);
@@ -2336,7 +2537,7 @@ with a const size by whether they are prepared at parse time or not.
                if (!parse_time)
                        return 1;
                if (type->array.member->size <= 0)
-                       return 0;       // UNTESTED
+                       return 0;       // NOTEST - error caught before here
 
                type->array.static_size = 1;
                type->size = type->array.size * type->array.member->size;
@@ -2380,26 +2581,23 @@ with a const size by whether they are prepared at parse time or not.
                        free(ptr);
        }
 
-       static int array_compat(struct type *require, struct type *have)
+       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) {
-                       if (have->array.vsize && require->array.vsize &&
-                           have->array.vsize != require->array.vsize)  // UNTESTED
-                               /* sizes might not be the same */
-                               return 0;       // UNTESTED
-                       return 1;
-               }
+               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;       // UNTESTED
+                       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;       // UNTESTED
+               return require->array.vsize == have->array.vsize;
        }
 
        static void array_print_type(struct type *type, FILE *f)
@@ -2473,29 +2671,18 @@ with a const size by whether they are prepared at parse time or not.
                $0->array.vsize = v;
        } }$
 
-###### Grammar
-       $*type
-       OptType -> Type ${ $0 = $<1; }$
-               | ${ $0 = NULL; }$
-
 ###### formal type grammar
 
-       | [ IDENTIFIER :: OptType ] Type ${ {
-               struct variable *v = var_decl(c, $ID.txt);
-
-               v->type = $<OT;
-               v->constant = 1;
-               if (!v->type)
-                       v->type = Tnum;
-               $0 = add_anon_type(c, &array_prototype, "array[var]");
-               $0->array.member = $<6;
+       | [ ] Type ${ {
+               $0 = add_anon_type(c, &array_prototype, "array[]");
+               $0->array.member = $<Type;
                $0->array.size = 0;
                $0->array.unspec = 1;
-               $0->array.vsize = v;
+               $0->array.vsize = NULL;
        } }$
 
 ###### Binode types
-       Index,
+       Index, Length,
 
 ###### term grammar
 
@@ -2507,6 +2694,13 @@ with a const size by whether they are prepared at parse time or not.
                $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);
@@ -2515,15 +2709,21 @@ with a const size by whether they are prepared at parse time or not.
                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, Tnum, 0);
-               t = propagate_types(b->left, c, perr, NULL, rules & Rnoconstant);
+               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);
+                       type_err(c, "error: %1 cannot be indexed", prog, t, 0,
+                                NULL);
                        return NULL;
                } else {
                        if (!type_compat(type, t->array.member, rules)) {
@@ -2534,6 +2734,21 @@ with a const size by whether they are prepared at parse time or not.
                }
                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;
@@ -2559,6 +2774,13 @@ with a const size by whether they are prepared at parse time or not.
                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
 
@@ -2620,6 +2842,8 @@ function will be needed.
 
 ###### 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
 
@@ -2724,12 +2948,41 @@ function will be needed.
                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
@@ -2751,7 +3004,7 @@ function will be needed.
                break;
 
 ###### declare terminals
-       $TERM struct .
+       $TERM struct
 
 ###### term grammar
 
@@ -2773,16 +3026,6 @@ function will be needed.
                break;
        }
 
-###### ast functions
-       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;
-       }
-
 ###### propagate exec cases
 
        case Xfieldref:
@@ -2790,24 +3033,15 @@ function will be needed.
                struct fieldref *f = cast(fieldref, prog);
                struct type *st = propagate_types(f->left, c, perr, NULL, 0);
 
-               if (!st)
-                       type_err(c, "error: unknown type for field access", f->left,    // UNTESTED
-                                NULL, 0, NULL);
-               else if (st->init != structure_init)
-                       type_err(c, "error: field reference attempted on %1, not a struct",
+               if (!st || !st->fieldref)
+                       type_err(c, "error: field reference on %1 is not supported",
                                 f->left, st, 0, NULL);
-               else if (f->index == IndexUnknown) {
-                       f->index = find_struct_index(st, f->name);
-                       if (f->index < 0)
-                               type_err(c, "error: cannot find requested field in %1",
-                                        f->left, st, 0, NULL);
-               }
-               if (f->index >= 0) {
-                       struct type *ft = st->structure.fields[f->index].type;
-                       if (!type_compat(type, ft, rules))
+               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,
-                                        ft, rules, type);
-                       return ft;
+                                        t, rules, type);
+                       return t;
                }
                break;
        }
@@ -2818,30 +3052,38 @@ function will be needed.
                struct fieldref *f = cast(fieldref, e);
                struct type *ltype;
                struct value *lleft = linterp_exec(c, f->left, &ltype);
-               lrv = (void*)lleft->ptr + ltype->structure.fields[f->index].offset;
-               rvtype = ltype->structure.fields[f->index].type;
+               lrv = lleft;
+               rvtype = ltype->fieldref(ltype, c, f, &lrv);
                break;
        }
 
 ###### top level grammar
-       DeclareStruct -> struct IDENTIFIER FieldBlock Newlines ${ {
-               struct type *t;
-               t = find_type(c, $ID.txt);
-               if (!t)
-                       t = add_type(c, $ID.txt, &structure_prototype);
-               else if (t->size >= 0) {
+       $*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 declartion", &t->first_use);
-                       /* Create a new one - duplicate */
-                       t = add_type(c, $ID.txt, &structure_prototype);
-               } else {
-                       struct type tmp = *t;
-                       *t = structure_prototype;
-                       t->name = tmp.name;
-                       t->next = tmp.next;
+                       tok_err(c, "info: this is location of declaration",
+                               &t->first_use);
+                       t = NULL;
                }
-               t->structure.field_list = $<FB;
+               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
@@ -2851,10 +3093,17 @@ function will be needed.
        | SimpleFieldList EOL ${ $0 = $<SFL; }$
 
        FieldLines -> SimpleFieldList Newlines ${ $0 = $<SFL; }$
-       | FieldLines SimpleFieldList Newlines ${
-               $SFL->prev = $<FL;
-               $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 ${
@@ -2896,10 +3145,10 @@ function will be needed.
                        if (fl->type->print && fl->init) {
                                fprintf(f, " = ");
                                if (fl->type == Tstr)
-                                       fprintf(f, "\"");       // UNTESTED
+                                       fprintf(f, "\"");
                                print_value(fl->type, fl->init, f);
                                if (fl->type == Tstr)
-                                       fprintf(f, "\"");       // UNTESTED
+                                       fprintf(f, "\"");
                        }
                        fprintf(f, "\n");
                }
@@ -2932,8 +3181,9 @@ function will be needed.
 #### References
 
 References, or pointers, are values that refer to another value.  They
-can only refer to a `struct`, though as a struct can embed anything they
-can effectively refer to anything.
+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
@@ -2979,14 +3229,14 @@ 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, arguments 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.
+`@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 @
@@ -3025,8 +3275,12 @@ anything in the heap or on the stack.  A reference can be assigned
                /* Nothing to do here */
        }
 
-       static int reference_compat(struct type *require, struct type *have)
+       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)
@@ -3039,6 +3293,22 @@ anything in the heap or on the stack.  A reference can be assigned
                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);
+               }
+               type_err(c, "error: field reference on %1 is not supported",
+                                f->left, rt, 0, NULL);
+               return Tnone;
+       }
+
        static struct type reference_prototype = {
                .print_type = reference_print_type,
                .cmp_eq = reference_cmp,
@@ -3046,6 +3316,7 @@ anything in the heap or on the stack.  A reference can be assigned
                .test = reference_test,
                .free = reference_free,
                .compat = reference_compat,
+               .fieldref = reference_fieldref,
                .size = sizeof(void*),
                .align = sizeof(void*),
        };
@@ -3149,6 +3420,7 @@ anything in the heap or on the stack.  A reference can be assigned
                                r->reftype = type;
                                *perr |= Eretry;
                        }
+                       *perr |= Erval;
                        return type;
                case RefNil:
                        if (type && type->free != reference_free)
@@ -3158,9 +3430,10 @@ anything in the heap or on the stack.  A reference can be assigned
                                r->reftype = type;
                                *perr |= Eretry;
                        }
+                       *perr |= Erval;
                        return type;
                case RefFree:
-                       t = propagate_types(r->right, c, perr, NULL, 0);
+                       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);
@@ -3170,7 +3443,6 @@ anything in the heap or on the stack.  A reference can be assigned
                break;  // NOTEST
        }
 
-
 ###### interp exec cases
        case Xref: {
                struct ref *r = cast(ref, e);
@@ -3205,7 +3477,7 @@ anything in the heap or on the stack.  A reference can be assigned
 ###### Expressions: dereference
 
 ###### Binode types
-       Deref,
+       Deref, AddressOf,
 
 ###### term grammar
 
@@ -3221,26 +3493,46 @@ anything in the heap or on the stack.  A reference can be assigned
                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: {
+       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
 
@@ -3254,7 +3546,7 @@ list, such as
 
 ##### Example: function 1
 
-       func main(av:[ac::number]string; env:[envc::number]string)
+       func main(av:[]string; env:[]string)
                code block
 
 or as an indented list of one parameter per line (though each line can
@@ -3263,8 +3555,8 @@ be a ';' separated list)
 ##### Example: function 2
 
        func main
-               argv:[argc::number]string
-               env:[envc::number]string
+               argv:[]string
+               env:[]string
        do
                code block
 
@@ -3332,12 +3624,21 @@ further detailed when Expression Lists are introduced.
                val->function = NULL;
        }
 
-       static int function_compat(struct type *require, struct type *have)
+       static int function_compat(struct type *require, struct type *have,
+                                  enum val_rules rules)
        {
                // FIXME can I do anything here yet?
                return 0;
        }
 
+       static struct exec *take_addr(struct exec *e)
+       {
+               struct binode *rv = new(binode);
+               rv->op = AddressOf;
+               rv->left = e;
+               return rv;
+       }
+
        static void function_check_args(struct parse_context *c, enum prop_err *perr,
                                        struct type *require, struct exec *args)
        {
@@ -3349,13 +3650,22 @@ further detailed when Expression Lists are introduced.
 
                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;
-                       propagate_types(arg->left, c, perr, pv->var->type, 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);
                }
@@ -3366,6 +3676,7 @@ further detailed when Expression Lists are introduced.
 
        static void function_print(struct type *type, struct value *val, FILE *f)
        {
+               fprintf(f, "\n");
                print_exec(val->function, 1, 0);
        }
 
@@ -3399,7 +3710,6 @@ further detailed when Expression Lists are introduced.
                        } else
                                type_print(type->function.return_type, f);
                }
-               fprintf(f, "\n");
        }
 
        static void function_free_type(struct type *t)
@@ -3422,9 +3732,6 @@ further detailed when Expression Lists are introduced.
 
        $TERM func
 
-###### Binode types
-       List,
-
 ###### Grammar
 
        $*variable
@@ -3557,10 +3864,11 @@ it in the "SimpleStatement Grammar" which will be described later.
                                 prog, NULL, 0, NULL);
                        return NULL;
                }
-               *perr |= Enoconst;
-               v->var->type->check_args(c, perr, v->var->type, args);
+               *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;
        }
 
@@ -3602,7 +3910,7 @@ it in the "SimpleStatement Grammar" which will be described later.
 
 ## Complex executables: statements and expressions
 
-Now that we have types and values and variables and most of the basic
+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.
@@ -3628,7 +3936,7 @@ 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 values.  Between `if` and `else` there is no
+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.
 
@@ -3673,9 +3981,9 @@ there.
                struct binode *b2 = cast(binode, b->right);
                struct type *t2;
 
-               propagate_types(b->left, c, perr, Tbool, 0);
-               t = propagate_types(b2->left, c, perr, type, Rnolabel);
-               t2 = propagate_types(b2->right, c, perr, type ?: t, Rnolabel);
+               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;
        }
 
@@ -3685,75 +3993,21 @@ there.
                struct binode *b2 = cast(binode, b->right);
                left = interp_exec(c, b->left, &ltype);
                if (left.bool)
-                       rv = interp_exec(c, b2->left, &rvtype); // UNTESTED
+                       rv = interp_exec(c, b2->left, &rvtype);
                else
                        rv = interp_exec(c, b2->right, &rvtype);
                }
                break;
 
-### Expression list
-
-We take a brief detour, now that we have expressions, to describe lists
-of expressions.  These will be needed for function parameters and
-possibly other situations.  They seem generic enough to introduce here
-to be used elsewhere.
-
-And ExpressionList will use the `List` type of `binode`, building up at
-the end.  And place where they are used will probably call
-`reorder_bilist()` to get a more normal first/next arrangement.
-
-###### declare terminals
-       $TERM ,
-
-`List` execs have no implicit semantics, so they are never propagated or
-interpreted.  The can be printed as a comma separate list, which is how
-they are parsed.  Note they are also used for function formal parameter
-lists.  In that case a separate function is used to print them.
-
-###### print binode cases
-       case List:
-               while (b) {
-                       printf(" ");
-                       print_exec(b->left, -1, bracket);
-                       if (b->right)
-                               printf(",");
-                       b = cast(binode, b->right);
-               }
-               break;
-
-###### propagate binode cases
-       case List: abort(); // NOTEST
-###### interp binode cases
-       case List: abort(); // NOTEST
-
-###### 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;
-       }$
-
 ### Expressions: Boolean
 
 The next class of expressions to use the `binode` will be Boolean
-expressions.  "`and then`" and "`or else`" are similar to `and` and `or`
-have same corresponding precendence.  The difference is that they don't
+expressions.  `and` and `or` are short-circuit operators that don't
 evaluate the second expression if not necessary.
 
 ###### Binode types
        And,
-       AndThen,
        Or,
-       OrElse,
        Not,
 
 ###### declare terminals
@@ -3769,14 +4023,6 @@ evaluate the second expression if not necessary.
                b->right = $<3;
                $0 = b;
        } }$
-       | Expression or else Expression ${ {
-               struct binode *b = new(binode);
-               b->op = OrElse;
-               b->left = $<1;
-               b->right = $<4;
-               $0 = b;
-       } }$
-
        | Expression and Expression ${ {
                struct binode *b = new(binode);
                b->op = And;
@@ -3784,14 +4030,6 @@ evaluate the second expression if not necessary.
                b->right = $<3;
                $0 = b;
        } }$
-       | Expression and then Expression ${ {
-               struct binode *b = new(binode);
-               b->op = AndThen;
-               b->left = $<1;
-               b->right = $<4;
-               $0 = b;
-       } }$
-
        | not Expression ${ {
                struct binode *b = new(binode);
                b->op = Not;
@@ -3807,13 +4045,6 @@ evaluate the second expression if not necessary.
                print_exec(b->right, -1, bracket);
                if (bracket) printf(")");
                break;
-       case AndThen:
-               if (bracket) printf("(");
-               print_exec(b->left, -1, bracket);
-               printf(" and then ");
-               print_exec(b->right, -1, bracket);
-               if (bracket) printf(")");
-               break;
        case Or:
                if (bracket) printf("(");
                print_exec(b->left, -1, bracket);
@@ -3821,13 +4052,6 @@ evaluate the second expression if not necessary.
                print_exec(b->right, -1, bracket);
                if (bracket) printf(")");
                break;
-       case OrElse:
-               if (bracket) printf("(");
-               print_exec(b->left, -1, bracket);
-               printf(" or else ");
-               print_exec(b->right, -1, bracket);
-               if (bracket) printf(")");
-               break;
        case Not:
                if (bracket) printf("(");
                printf("not ");
@@ -3837,9 +4061,7 @@ evaluate the second expression if not necessary.
 
 ###### propagate binode cases
        case And:
-       case AndThen:
        case Or:
-       case OrElse:
        case Not:
                /* both must be Tbool, result is Tbool */
                propagate_types(b->left, c, perr, Tbool, 0);
@@ -3847,25 +4069,16 @@ evaluate the second expression if not necessary.
                if (type && type != Tbool)
                        type_err(c, "error: %1 operation found where %2 expected", prog,
                                   Tbool, 0, type);
+               *perr |= Erval;
                return Tbool;
 
 ###### interp binode cases
        case And:
-               rv = interp_exec(c, b->left, &rvtype);
-               right = interp_exec(c, b->right, &rtype);
-               rv.bool = rv.bool && right.bool;
-               break;
-       case AndThen:
                rv = interp_exec(c, b->left, &rvtype);
                if (rv.bool)
                        rv = interp_exec(c, b->right, NULL);
                break;
        case Or:
-               rv = interp_exec(c, b->left, &rvtype);
-               right = interp_exec(c, b->right, &rtype);
-               rv.bool = rv.bool || right.bool;
-               break;
-       case OrElse:
                rv = interp_exec(c, b->left, &rvtype);
                if (!rv.bool)
                        rv = interp_exec(c, b->right, NULL);
@@ -3957,17 +4170,18 @@ expression operator, and the `CMPop` non-terminal will match one of them.
        case Eql:
        case NEql:
                /* Both must match but not be labels, result is Tbool */
-               t = propagate_types(b->left, c, perr, NULL, Rnolabel);
+               t = propagate_types(b->left, c, perr, NULL, 0);
                if (t)
                        propagate_types(b->right, c, perr, t, 0);
                else {
-                       t = propagate_types(b->right, c, perr, NULL, Rnolabel); // UNTESTED
-                       if (t)  // UNTESTED
-                               t = propagate_types(b->left, c, perr, t, 0);    // UNTESTED
+                       t = propagate_types(b->right, c, perr, NULL, 0); // NOTEST
+                       if (t)  // NOTEST
+                               t = propagate_types(b->left, c, perr, t, 0);    // NOTEST
                }
                if (!type_compat(type, Tbool, 0))
                        type_err(c, "error: Comparison returns %1 but %2 expected", prog,
                                    Tbool, rules, type);
+               *perr |= Erval;
                return Tbool;
 
 ###### interp binode cases
@@ -4000,13 +4214,13 @@ expression operator, and the `CMPop` non-terminal will match one of them.
 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 uniary.
+than the unary.
 
-Testing comes in two forms.  A single question mark (`?`) is a uniary
+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 value value" and there will be more uses as the language
-develops.  A double questionmark (`??`) is a binary operator (Choose),
-with same precedence as multiplication, which returns the LHS if it
+"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
@@ -4124,9 +4338,10 @@ parentheses around an expression converts it into a Term,
                if (bracket) printf(")");
                break;
        case Bracket:
-               printf("(");
+               /* Avoid double brackets... */
+               if (!bracket) printf("(");
                print_exec(b->right, indent, bracket);
-               printf(")");
+               if (!bracket) printf(")");
                break;
 
 ###### propagate binode cases
@@ -4145,6 +4360,7 @@ parentheses around an expression converts it into a Term,
                if (!type_compat(type, Tnum, 0))
                        type_err(c, "error: Arithmetic returns %1 but %2 expected", prog,
                                   Tnum, rules, type);
+               *perr |= Erval;
                return Tnum;
 
        case Concat:
@@ -4154,15 +4370,17 @@ parentheses around an expression converts it into a Term,
                if (!type_compat(type, Tstr, 0))
                        type_err(c, "error: Concat returns %1 but %2 expected", prog,
                                   Tstr, rules, type);
+               *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,     // UNTESTED
+                       type_err(c,
                          "error: Can only convert string to number, not %1",
                                prog, type, 0, NULL);
+               *perr |= Erval;
                return Tnum;
 
        case Test:
@@ -4171,6 +4389,7 @@ parentheses around an expression converts it into a Term,
                if (!t || !t->test)
                        type_err(c, "error: '?' requires a testable value, not %1",
                                 prog, t, 0, NULL);
+               *perr |= Erval;
                return Tbool;
 
        case Choose:
@@ -4182,10 +4401,11 @@ parentheses around an expression converts it into a Term,
                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, perr, type, 0);
+               return propagate_types(b->right, c, perr, type, rules);
 
 ###### interp binode cases
 
@@ -4247,19 +4467,19 @@ parentheses around an expression converts it into a Term,
                rvtype = Tnum;
 
                struct text tx = right.str;
-               char tail[3];
+               char tail[3] = "";
                int neg = 0;
                if (tx.txt[0] == '-') {
-                       neg = 1;        // UNTESTED
-                       tx.txt++;       // UNTESTED
-                       tx.len--;       // UNTESTED
+                       neg = 1;
+                       tx.txt++;
+                       tx.len--;
                }
                if (number_parse(rv.num, tail, tx) == 0)
-                       mpq_init(rv.num);       // UNTESTED
+                       mpq_init(rv.num);
                else if (neg)
-                       mpq_neg(rv.num, rv.num);        // UNTESTED
+                       mpq_neg(rv.num, rv.num);
                if (tail[0])
-                       printf("Unsupported suffix: %.*s\n", tx.len, tx.txt);   // UNTESTED
+                       printf("Unsupported suffix: %.*s\n", tx.len, tx.txt);
 
                break;
        case Test:
@@ -4328,14 +4548,8 @@ 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`
@@ -4368,7 +4582,7 @@ the common header for all reductions to use.
        |        OpenScope SimpleStatements EOL ${ $0 = reorder_bilist($<SS); }$
        |        IN OpenScope OptNL Statementlist OUT ${ $0 = $<Sl; }$
 
-       UseBlock -> { OpenScope IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+       UseBlock -> { IN OpenScope OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
        |        { OpenScope SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
        |        IN OpenScope OptNL Statementlist OUT ${ $0 = $<Sl; }$
 
@@ -4382,7 +4596,7 @@ the common header for all reductions to use.
 
        ComplexStatements -> ComplexStatements ComplexStatement ${
                if ($2 == NULL) {
-                       $0 = $<1;
+                       $0 = $<1;       // NOTEST - impossible
                } else {
                        $0 = new(binode);
                        $0->op = Block;
@@ -4392,7 +4606,7 @@ the common header for all reductions to use.
        }$
        | ComplexStatement ${
                if ($1 == NULL) {
-                       $0 = NULL;
+                       $0 = NULL;      // NOTEST - impossible
                } else {
                        $0 = new(binode);
                        $0->op = Block;
@@ -4432,25 +4646,13 @@ the common header for all reductions to use.
 
 ###### print binode cases
        case Block:
-               if (indent < 0) {
-                       // simple statement
-                       if (b->left == NULL)    // UNTESTED
-                               printf("pass"); // UNTESTED
-                       else
-                               print_exec(b->left, indent, bracket);   // UNTESTED
-                       if (b->right) { // UNTESTED
-                               printf("; ");   // UNTESTED
-                               print_exec(b->right, indent, bracket);  // UNTESTED
-                       }
-               } 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
@@ -4466,7 +4668,9 @@ the common header for all reductions to use.
                struct binode *e;
 
                for (e = b; e; e = cast(binode, e->right)) {
-                       t = propagate_types(e->left, c, perr, NULL, rules);
+                       *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)
@@ -4478,7 +4682,7 @@ the common header for all reductions to use.
                                if (!type)
                                        type = t;
                                else if (t != type)
-                                       type_err(c, "error: expected %1%r, found %2",
+                                       type_err(c, "error: expected %1, found %2",
                                                 e->left, type, rules, t);
                        }
                }
@@ -4502,7 +4706,7 @@ expressions and prints the values separated by spaces and terminated
 by a newline.  No control of formatting is possible.
 
 `print` uses `ExpressionList` to collect the expressions and stores them
-on the left side of a `Print` binode unlessthere is a trailing comma
+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.
 
@@ -4537,11 +4741,16 @@ printed.
 
        case Print:
                do_indent(indent, "print");
-               if (b->right) {
-                       print_exec(b->right, -1, bracket);
+               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(",");
-               } else
-                       print_exec(b->left, -1, bracket);
                if (indent >= 0)
                        printf("\n");
                break;
@@ -4555,7 +4764,7 @@ printed.
                else
                        b = cast(binode, b->right);
                while (b) {
-                       propagate_types(b->left, c, perr, NULL, Rnolabel);
+                       propagate_types(b->left, c, perr_local, NULL, 0);
                        b = cast(binode, b->right);
                }
                break;
@@ -4586,9 +4795,9 @@ An assignment will assign a value to a variable, providing it hasn't
 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,
@@ -4667,29 +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,
+               /* 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
                 */
-               t = propagate_types(b->left, c, perr, NULL,
-                                   Rnolabel | (b->op == Assign ? Rnoconstant : 0));
+               *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, perr, 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, NULL);
+                       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, perr, NULL, Rnolabel);
+                       t = propagate_types(b->right, c, perr_local, NULL, 0);
                        if (t)
-                               propagate_types(b->left, c, perr, t,
-                                               (b->op == Assign ? Rnoconstant : 0));
+                               propagate_types(b->left, c, perr, t, 0);
                }
-               if (t && t->dup == NULL && !(*perr & Emaycopy))
+               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;
@@ -4711,10 +4944,10 @@ it is declared, and error will be raised as the name is created as
                val = var_value(c, v);
                if (v->type->prepare_type)
                        v->type->prepare_type(c, v->type, 0);
-               if (b->right)
-                       dinterp_exec(c, b->right, val, v->type, 0);
-               else
+               if (!b->right)
                        val_init(v->type, val);
+               else
+                       dinterp_exec(c, b->right, val, v->type, 0);
                break;
        }
 
@@ -4736,17 +4969,6 @@ function which has a return type, and the "condition" code blocks in
                $0 = b = new_pos(binode, $1);
                b->op = Use;
                b->right = $<2;
-               if (b->right->type == Xvar) {
-                       struct var *v = cast(var, b->right);
-                       if (v->var->type == Tnone) {
-                               /* Convert this to a label */
-                               struct value *val;
-
-                               v->var->type = Tlabel;
-                               val = global_alloc(c, Tlabel, v->var, NULL);
-                               val->label = val;
-                       }
-               }
        }$
 
 ###### print binode cases
@@ -5149,9 +5371,7 @@ casepart` to track a list of case parts.
 
 ###### propagate binode cases
        case Loop:
-               t = propagate_types(b->right, c, perr, Tnone, 0);
-               if (!type_compat(Tnone, t, 0))
-                       *perr |= Efail; // UNTESTED
+               propagate_types(b->right, c, perr_local, Tnone, 0);
                return propagate_types(b->left, c, perr, type, rules);
 
 ###### propagate exec cases
@@ -5171,13 +5391,9 @@ casepart` to track a list of case parts.
                struct casepart *cp;
 
                t = propagate_types(cs->forpart, c, perr, Tnone, 0);
-               if (!type_compat(Tnone, t, 0))
-                       *perr |= Efail; // UNTESTED
 
                if (cs->looppart) {
                        t = propagate_types(cs->thenpart, c, perr, Tnone, 0);
-                       if (!type_compat(Tnone, t, 0))
-                               *perr |= Efail; // UNTESTED
                }
                if (cs->casepart == NULL) {
                        propagate_types(cs->condpart, c, perr, Tbool, 0);
@@ -5189,9 +5405,12 @@ casepart` to track a list of case parts.
                             cp && !t; cp = cp->next)
                                t = propagate_types(cp->value, c, perr, NULL, 0);
                        if (!t && cs->condpart)
-                               t = propagate_types(cs->condpart, c, perr, NULL, Rboolok);      // UNTESTED
+                               t = propagate_types(cs->condpart, c, perr, // NOTEST
+                                                   NULL, Rboolok);
                        if (!t && cs->looppart)
-                               t = propagate_types(cs->looppart, c, perr, NULL, Rboolok);      // UNTESTED
+       
+                       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)
@@ -5207,8 +5426,8 @@ casepart` to track a list of case parts.
                        type = propagate_types(cs->elsepart, c, perr, NULL, rules);
                for (cp = cs->casepart;
                     cp && !type;
-                    cp = cp->next)     // UNTESTED
-                       type = propagate_types(cp->action, c, perr, NULL, rules);       // UNTESTED
+                    cp = cp->next)     // NOTEST
+                       type = propagate_types(cp->action, c, perr, NULL, rules);       // NOTEST
                if (type) {
                        if (!cs->looppart)
                                propagate_types(cs->thenpart, c, perr, type, rules);
@@ -5279,12 +5498,8 @@ All the language elements so far can be used in various places.  Now
 it is time to clarify what those places are.
 
 At the top level of a file there will be a number of declarations.
-Many of the things that can be declared haven't been described yet,
-such as functions, procedures, imports, and probably more.
-For now there are two sorts of things that can appear at the top
-level.  They are predefined constants, `struct` types, and the `main`
-function.  While the syntax will allow the `main` function to appear
-multiple times, that will trigger an error if it is actually attempted.
+These can be for predefined constants, `struct` types, and functions -
+particularly the `main` function.
 
 The various declarations do not return anything.  They store the
 various declarations in the parse context.
@@ -5306,7 +5521,7 @@ various declarations in the parse context.
        | DeclarationList Declaration
 
        Declaration -> ERROR Newlines ${
-               tok_err(c,      // UNTESTED
+               tok_err(c,      // NOTEST
                        "error: unhandled parse error", &$1);
        }$
        | DeclareConstant
@@ -5325,9 +5540,9 @@ 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 be used to evaluate a constant, but it will not have
-access to any program state, once such statement becomes meaningful.
-e.g.  arguments and filesystem will not be visible.
+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.
@@ -5427,7 +5642,7 @@ constants.
                                } while (perr & Eretry);
                                if (perr & Efail)
                                        c->parse_error += 1;
-                               else if (!(perr & Enoconst)) {
+                               else if (!(perr & Eruntime)) {
                                        progress = some;
                                        struct value res = interp_exec(
                                                c, vb->right, &v->var->type);
@@ -5535,8 +5750,8 @@ is a bit more interesting at this level.
                        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, 
+                       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);
@@ -5612,10 +5827,13 @@ is a bit more interesting at this level.
                                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)
-                                       print_exec(val->function, 0, brackets);
-                               else
+                               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;
                        }
@@ -5648,7 +5866,7 @@ is a bit more interesting at this level.
                                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", 
+                               type_err(c, "error: function cannot return value of type %1",
                                         v->where_decl, v->type->function.return_type, 0, NULL);
                        }
 
@@ -5717,15 +5935,12 @@ is a bit more interesting at this level.
                        struct value *vl = var_value(c, v->var);
                        struct value arg;
                        struct type *t;
-                       mpq_t argcq;
                        int i;
 
                        switch (anum++) {
                        case 0: /* argv */
                                t = v->var->type;
-                               mpq_init(argcq);
-                               mpq_set_ui(argcq, argc, 1);
-                               memcpy(var_value(c, t->array.vsize), &argcq, sizeof(argcq));
+                               t->array.size = argc;
                                t->prepare_type(c, t, 0);
                                array_init(v->var->type, vl);
                                for (i = 0; i < argc; i++) {
@@ -5746,11 +5961,6 @@ is a bit more interesting at this level.
                c->local = NULL;
        }
 
-###### ast functions
-       void free_variable(struct variable *v)
-       {
-       }
-
 ## And now to test it out.
 
 Having a language requires having a "hello world" program.  I'll
@@ -5779,7 +5989,12 @@ things which will likely grow as the languages grows.
                name:string
                alive:Boolean
 
-       func main(argv:[argc::]string)
+       func fibonacci(n:number):number
+               if n <= 2:
+                       use 1
+               else use fibonacci(n-1) + fibonacci(n-2)
+
+       func main(argv:[]string)
                print "Hello World, what lovely oceans you have!"
                print "Are there", five, "?"
                print pi, pie, "but", cake
@@ -5807,7 +6022,7 @@ things which will likely grow as the languages grows.
                a : number
                a = A;
                b:number = B
-               if a > 0 and then b > 0:
+               if a > 0 and b > 0:
                        while a != b:
                                if a < b:
                                        b = b - a
@@ -5830,6 +6045,13 @@ things which will likely grow as the languages grows.
                        f1 = f2
                        f2 = f3
                print ""
+               for
+                       f := 1
+                       print "Fibonacci:",
+               then f = f + 1
+               while f < 13:
+                       print "", fibonacci(f),
+               print
 
                /* Binary search... */
                for
@@ -5838,19 +6060,19 @@ things which will likely grow as the languages grows.
                while
                        mid := (lo + hi) / 2
                        if mid == target:
-                               use Found
+                               use .Found
                        if mid < target:
                                lo = mid
                        else
                                hi = mid
                        if hi - lo < 1:
                                lo = mid
-                               use GiveUp
+                               use .GiveUp
                        use True
                do pass
-               case Found:
+               case .Found:
                        print "Yay, I found", target
-               case GiveUp:
+               case .GiveUp:
                        print "Closest I found was", lo
 
                size::= 10