]> ocean-lang.org Git - ocean/blobdiff - csrc/oceani.mdc
oceani: improve construction of per-function stack frame
[ocean] / csrc / oceani.mdc
index 8fef86460ab64f08583b8ec2776d945bc302771b..c2a19ce0c7744190c2fedba910f7205c851e83db 100644 (file)
@@ -41,10 +41,12 @@ 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).
 
-Elements that are present purely to make a usable language, and
-without any expectation that they will remain, are the "program'
-clause, which provides a list of variables to received command-line
-arguments, and the "print" statement which performs simple output.
+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
@@ -72,7 +74,8 @@ So the main requirements of the interpreter are:
 - Parse the program, possibly with tracing,
 - Analyse the parsed program to ensure consistency,
 - Print the program,
-- Execute the program, if no parsing or consistency errors were found.
+- 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`.
@@ -81,7 +84,7 @@ 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`
+can contain multiple programs This is effected with the `--section`
 option.
 
 This code must be compiled with `-fplan9-extensions` so that anonymous
@@ -105,12 +108,12 @@ structures can be used.
 
 ###### Parser: header
        ## macros
+       struct parse_context;
        ## ast
        struct parse_context {
                struct token_config config;
                char *file_name;
                int parse_error;
-               struct exec *prog;
                ## parse context
        };
 
@@ -151,8 +154,8 @@ structures can be used.
        ## core functions
 
        #include <getopt.h>
-       static char Usage[] = "Usage: oceani --trace --print --noexec --brackets"
-                             "--section=SectionName prog.ocn\n";
+       static char Usage[] =
+               "Usage: oceani --trace --print --noexec --brackets --section=SectionName prog.ocn\n";
        static const struct option long_options[] = {
                {"trace",     0, NULL, 't'},
                {"print",     0, NULL, 'p'},
@@ -162,18 +165,22 @@ structures can be used.
                {NULL,        0, NULL, 0},
        };
        const char *options = "tpnbs";
+
+       static void pr_err(char *msg)                   // NOTEST
+       {
+               fprintf(stderr, "%s\n", msg);           // NOTEST
+       }                                               // NOTEST
+
        int main(int argc, char *argv[])
        {
                int fd;
                int len;
                char *file;
-               struct section *s, *ss;
+               struct section *s = NULL, *ss;
                char *section = NULL;
                struct parse_context context = {
                        .config = {
-                               .ignored = (1 << TK_line_comment)
-                                        | (1 << TK_block_comment)
-                                        | (1 << TK_mark),
+                               .ignored = (1 << TK_mark),
                                .number_chars = ".,_+- ",
                                .word_start = "_",
                                .word_cont = "_",
@@ -205,7 +212,7 @@ structures can be used.
                context.file_name = argv[optind];
                len = lseek(fd, 0, 2);
                file = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
-               s = code_extract(file, file+len, NULL);
+               s = code_extract(file, file+len, pr_err);
                if (!s) {
                        fprintf(stderr, "oceani: could not find any code in %s\n",
                                argv[optind]);
@@ -224,38 +231,42 @@ structures can be used.
                        if (!ss) {
                                fprintf(stderr, "oceani: cannot find section %s\n",
                                        section);
-                               exit(1);
+                               goto cleanup;
                        }
                } else
-                       ss = s;
+                       ss = s;                         // NOTEST
+               if (!ss->code) {
+                       fprintf(stderr, "oceani: no code found in requested section\n");        // NOTEST
+                       goto cleanup;                   // NOTEST
+               }
+
                parse_oceani(ss->code, &context.config, dotrace ? stderr : NULL);
 
-               if (!context.prog) {
-                       fprintf(stderr, "oceani: no program found.\n");
+               if (!context.parse_error && !analyse_funcs(&context)) {
+                       fprintf(stderr, "oceani: type error in program - not running.\n");
                        context.parse_error = 1;
                }
-               if (context.prog && doprint) {
+
+               if (doprint) {
                        ## print const decls
                        ## print type decls
-                       print_exec(context.prog, 0, brackets);
+                       ## print func decls
                }
-               if (context.prog && doexec && !context.parse_error) {
-                       if (!analyse_prog(context.prog, &context)) {
-                               fprintf(stderr, "oceani: type error in program - not running.\n");
-                               exit(1);
-                       }
-                       interp_prog(context.prog, argv+optind+1);
-               }
-               free_exec(context.prog);
-
+               if (doexec && !context.parse_error)
+                       interp_main(&context, argc - optind, argv + optind);
+       cleanup:
                while (s) {
                        struct section *t = s->next;
                        code_free(s->code);
                        free(s);
                        s = t;
                }
-               ## free context vars
+               // FIXME parser should pop scope even on error
+               while (context.scope_depth > 0)
+                       scope_pop(&context);
+               ## free global vars
                ## free context types
+               ## free context storage
                exit(context.parse_error ? 1 : 0);
        }
 
@@ -283,20 +294,12 @@ and the program will not run.
 
 If the same variable is declared in both branchs of an 'if/else', or
 in all cases of a 'switch' then the multiple instances may be merged
-into just one variable if the variable is references after the
+into just one variable if the variable is referenced after the
 conditional statement.  When this happens, the types must naturally be
 consistent across all the branches.  When the variable is not used
 outside the if, the variables in the different branches are distinct
 and can be of different types.
 
-Determining the types of all variables early is important for
-processing command line arguments.  These can be assigned to any of
-several types of variable, but we must first know the correct type so
-any required conversion can happen.  If a variable is associated with
-a command line argument but no type can be interpreted (e.g. the
-variable is only ever used in a `print` statement), then the type is
-set to 'string'.
-
 Undeclared names may only appear in "use" statements and "case" expressions.
 These names are given a type of "label" and a unique value.
 This allows them to fill the role of a name in an enumerated type, which
@@ -342,10 +345,10 @@ errors, each language element will need to record a file location
 element where its type was set.  For now we will assume that each line
 of an error message indicates one location in the file, and up to 2
 types.  So we provide a `printf`-like function which takes a format, a
-language (a `struct exec` which has not yet been introduced), and 2
+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 will be explained later, there are sometimes extra rules for
+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.
 
@@ -358,6 +361,9 @@ context so indicate that parsing failed.
 ###### 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);
 
 ###### core functions
 
@@ -402,34 +408,34 @@ 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.  These will introduced and described
-here.  The following section will present the different specific code
-elements which comprise or manipulate these various entities.
+confusing) and introduced here.  The following section will present the
+different specific code elements which comprise or manipulate these
+various entities.
 
 ### Types
 
 Values come in a wide range of types, with more likely to be added.
-Each type needs to be able to parse and print its own values (for
-convenience at least) as well as to compare two values, at least for
-equality and possibly for order.  For now, values might need to be
-duplicated and freed, though eventually such manipulations will be
-better integrated into the language.
+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, multiple, 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 eventaully enable types to
+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.
 
-Named type are stored in a simple linked list.  Objects of each type are "values"
-which are often passed around by value.
+Named type are stored in a simple linked list.  Objects of each type are
+"values" which are often passed around by value.
 
 ###### ast
 
        struct value {
-               struct type *type;
                union {
+                       char ptr[1];
                        ## value union fields
                };
        };
@@ -437,17 +443,18 @@ which are often passed around by value.
        struct type {
                struct text name;
                struct type *next;
-               struct value (*init)(struct type *type);
-               struct value (*prepare)(struct type *type);
-               struct value (*parse)(struct type *type, char *str);
-               void (*print)(struct value val);
+               int size, align;
+               void (*init)(struct type *type, struct value *val);
+               void (*prepare_type)(struct parse_context *c, struct type *type, int parse_time);
+               void (*print)(struct type *type, struct value *val);
                void (*print_type)(struct type *type, FILE *f);
-               int (*cmp_order)(struct value v1, struct value v2);
-               int (*cmp_eq)(struct value v1, struct value v2);
-               struct value (*dup)(struct value val);
-               void (*free)(struct value val);
+               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 (*free)(struct type *type, struct value *val);
                void (*free_type)(struct type *t);
-               int (*compat)(struct type *this, struct type *other);
                long long (*to_int)(struct value *v);
                double (*to_float)(struct value *v);
                int (*to_mpq)(mpq_t *q, struct value *v);
@@ -493,31 +500,18 @@ which are often passed around by value.
                 */
        }
 
-       static void free_value(struct value v)
+       static void free_value(struct type *type, struct value *v)
        {
-               if (v.type)
-                       v.type->free(v);
-       }
-
-       static int type_compat(struct type *require, struct type *have, int rules)
-       {
-               if ((rules & Rboolok) && have == Tbool)
-                       return 1;
-               if ((rules & Rnolabel) && have == Tlabel)
-                       return 0;
-               if (!require || !have)
-                       return 1;
-
-               if (require->compat)
-                       return require->compat(require, have);
-
-               return require == have;
+               if (type && v) {
+                       type->free(type, v);
+                       memset(v, 0x5a, type->size);
+               }
        }
 
        static void type_print(struct type *type, FILE *f)
        {
                if (!type)
-                       fputs("*unknown*type*", f);
+                       fputs("*unknown*type*", f);     // NOTEST
                else if (type->name.len)
                        fprintf(f, "%.*s", type->name.len, type->name.txt);
                else if (type->print_type)
@@ -526,70 +520,48 @@ which are often passed around by value.
                        fputs("*invalid*type*", f);     // NOTEST
        }
 
-       static struct value val_prepare(struct type *type)
+       static void val_init(struct type *type, struct value *val)
        {
-               struct value rv;
-
-               if (type)
-                       return type->prepare(type);
-               rv.type = type;
-               return rv;
-       }
-
-       static struct value val_init(struct type *type)
-       {
-               struct value rv;
-
-               if (type)
-                       return type->init(type);
-               rv.type = type;
-               return rv;
+               if (type && type->init)
+                       type->init(type, val);
        }
 
-       static struct value dup_value(struct value v)
+       static void dup_value(struct type *type,
+                             struct value *vold, struct value *vnew)
        {
-               if (v.type)
-                       return v.type->dup(v);
-               return v;
+               if (type && type->dup)
+                       type->dup(type, vold, vnew);
        }
 
-       static int value_cmp(struct value left, struct value right)
+       static int value_cmp(struct type *tl, struct type *tr,
+                            struct value *left, struct value *right)
        {
-               if (left.type && left.type->cmp_order)
-                       return left.type->cmp_order(left, right);
-               if (left.type && left.type->cmp_eq)
-                       return left.type->cmp_eq(left, right);
-               return -1;
+               if (tl && tl->cmp_order)
+                       return tl->cmp_order(tl, tr, left, right);
+               if (tl && tl->cmp_eq)                   // NOTEST
+                       return tl->cmp_eq(tl, tr, left, right); // NOTEST
+               return -1;                              // NOTEST
        }
 
-       static void print_value(struct value v)
+       static void print_value(struct type *type, struct value *v)
        {
-               if (v.type && v.type->print)
-                       v.type->print(v);
+               if (type && type->print)
+                       type->print(type, v);
                else
                        printf("*Unknown*");            // NOTEST
        }
 
-       static struct value parse_value(struct type *type, char *arg)
-       {
-               struct value rv;
-
-               if (type && type->parse)
-                       return type->parse(type, arg);
-               rv.type = NULL;                         // NOTEST
-               return rv;                              // NOTEST
-       }
-
 ###### forward decls
 
-       static void free_value(struct value v);
+       static void free_value(struct type *type, struct value *v);
        static int type_compat(struct type *require, struct type *have, int rules);
        static void type_print(struct type *type, FILE *f);
-       static struct value val_init(struct type *type);
-       static struct value dup_value(struct value v);
-       static int value_cmp(struct value left, struct value right);
-       static void print_value(struct value v);
-       static struct value parse_value(struct type *type, char *arg);
+       static void val_init(struct type *type, struct value *v);
+       static void dup_value(struct type *type,
+                             struct value *vold, struct value *vnew);
+       static int value_cmp(struct type *tl, struct type *tr,
+                            struct value *left, struct value *right);
+       static void print_value(struct type *type, struct value *v);
 
 ###### free context types
 
@@ -602,6 +574,30 @@ which are often passed around by value.
                free(t);
        }
 
+Type can be specified for local variables, for fields in a structure,
+for formal parameters to functions, and possibly elsewhere.  Different
+rules may apply in different contexts.  As a minimum, a named type may
+always be used.  Currently the type of a formal parameter can be
+different from types in other contexts, so we have a separate grammar
+symbol for those.
+
+###### Grammar
+
+       $*type
+       Type -> IDENTIFIER ${
+               $0 = find_type(c, $1.txt);
+               if (!$0) {
+                       tok_err(c,
+                               "error: undefined type", &$1);
+
+                       $0 = Tnone;
+               }
+       }$
+       ## type grammar
+
+       FormalType -> Type ${ $0 = $<1; }$
+       ## formal type grammar
+
 #### Base Types
 
 Values of the base types can be numbers, which we represent as
@@ -621,20 +617,31 @@ 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.
 
-When assigning command line arguments to variables, we need to be able
-to parse each type from a string.
+###### type functions
+
+       int (*compat)(struct type *this, struct type *other);
+
+###### ast functions
+
+       static int type_compat(struct type *require, struct type *have, int 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);
 
-The distinction beteen "prepare" and "init" needs to be explained.
-"init" sets up an initial value, such as "zero" or the empty string.
-"prepare" simply prepares the data structure so that if "free" gets
-called on it, it won't do something silly.  Normally a value will be
-stored after "prepare" but before "free", but this might not happen if
-there are errors.
+               return require == have;
+       }
 
 ###### includes
        #include <gmp.h>
-       #include "string.h"
-       #include "number.h"
+       #include "parse_string.h"
+       #include "parse_number.h"
 
 ###### libs
        myLDLIBS := libnumber.o libstring.o -lgmp
@@ -646,16 +653,18 @@ there are errors.
 ###### value union fields
        struct text str;
        mpq_t num;
-       int bool;
+       unsigned char bool;
        void *label;
 
 ###### ast functions
-       static void _free_value(struct value v)
+       static void _free_value(struct type *type, struct value *v)
        {
-               switch (v.type->vtype) {
+               if (!v)
+                       return;         // NOTEST
+               switch (type->vtype) {
                case Vnone: break;
-               case Vstr: free(v.str.txt); break;
-               case Vnum: mpq_clear(v.num); break;
+               case Vstr: free(v->str.txt); break;
+               case Vnum: mpq_clear(v->num); break;
                case Vlabel:
                case Vbool: break;
                }
@@ -663,112 +672,82 @@ there are errors.
 
 ###### value functions
 
-       static struct value _val_prepare(struct type *type)
-       {
-               struct value rv;
-
-               rv.type = type;
-               switch(type->vtype) {
-               case Vnone:
-                       break;
-               case Vnum:
-                       memset(&rv.num, 0, sizeof(rv.num));
-                       break;
-               case Vstr:
-                       rv.str.txt = NULL;
-                       rv.str.len = 0;
-                       break;
-               case Vbool:
-                       rv.bool = 0;
-                       break;
-               case Vlabel:
-                       rv.label = NULL;
-                       break;
-               }
-               return rv;
-       }
-
-       static struct value _val_init(struct type *type)
+       static void _val_init(struct type *type, struct value *val)
        {
-               struct value rv;
-
-               rv.type = type;
                switch(type->vtype) {
                case Vnone:             // NOTEST
                        break;          // NOTEST
                case Vnum:
-                       mpq_init(rv.num); break;
+                       mpq_init(val->num); break;
                case Vstr:
-                       rv.str.txt = malloc(1);
-                       rv.str.len = 0;
+                       val->str.txt = malloc(1);
+                       val->str.len = 0;
                        break;
                case Vbool:
-                       rv.bool = 0;
+                       val->bool = 0;
+                       break;
+               case Vlabel:
+                       val->label = NULL;
                        break;
-               case Vlabel:                    // NOTEST
-                       rv.label = NULL;        // NOTEST
-                       break;                  // NOTEST
                }
-               return rv;
        }
 
-       static struct value _dup_value(struct value v)
+       static void _dup_value(struct type *type,
+                              struct value *vold, struct value *vnew)
        {
-               struct value rv;
-               rv.type = v.type;
-               switch (rv.type->vtype) {
+               switch (type->vtype) {
                case Vnone:             // NOTEST
                        break;          // NOTEST
                case Vlabel:
-                       rv.label = v.label;
+                       vnew->label = vold->label;
                        break;
                case Vbool:
-                       rv.bool = v.bool;
+                       vnew->bool = vold->bool;
                        break;
                case Vnum:
-                       mpq_init(rv.num);
-                       mpq_set(rv.num, v.num);
+                       mpq_init(vnew->num);
+                       mpq_set(vnew->num, vold->num);
                        break;
                case Vstr:
-                       rv.str.len = v.str.len;
-                       rv.str.txt = malloc(rv.str.len);
-                       memcpy(rv.str.txt, v.str.txt, v.str.len);
+                       vnew->str.len = vold->str.len;
+                       vnew->str.txt = malloc(vnew->str.len);
+                       memcpy(vnew->str.txt, vold->str.txt, vnew->str.len);
                        break;
                }
-               return rv;
        }
 
-       static int _value_cmp(struct value left, struct value right)
+       static int _value_cmp(struct type *tl, struct type *tr,
+                             struct value *left, struct value *right)
        {
                int cmp;
-               if (left.type != right.type)
-                       return left.type - right.type;  // NOTEST
-               switch (left.type->vtype) {
-               case Vlabel: cmp = left.label == right.label ? 0 : 1; break;
-               case Vnum: cmp = mpq_cmp(left.num, right.num); break;
-               case Vstr: cmp = text_cmp(left.str, right.str); break;
-               case Vbool: cmp = left.bool - right.bool; break;
+               if (tl != tr)
+                       return tl - tr; // NOTEST
+               switch (tl->vtype) {
+               case Vlabel: cmp = left->label == right->label ? 0 : 1; break;
+               case Vnum: cmp = mpq_cmp(left->num, right->num); break;
+               case Vstr: cmp = text_cmp(left->str, right->str); break;
+               case Vbool: cmp = left->bool - right->bool; break;
                case Vnone: cmp = 0;                    // NOTEST
                }
                return cmp;
        }
 
-       static void _print_value(struct value v)
+       static void _print_value(struct type *type, struct value *v)
        {
-               switch (v.type->vtype) {
+               switch (type->vtype) {
                case Vnone:                             // NOTEST
                        printf("*no-value*"); break;    // NOTEST
                case Vlabel:                            // NOTEST
-                       printf("*label-%p*", v.label); break; // NOTEST
+                       printf("*label-%p*", v->label); break; // NOTEST
                case Vstr:
-                       printf("%.*s", v.str.len, v.str.txt); break;
+                       printf("%.*s", v->str.len, v->str.txt); break;
                case Vbool:
-                       printf("%s", v.bool ? "True":"False"); break;
+                       printf("%s", v->bool ? "True":"False"); break;
                case Vnum:
                        {
                        mpf_t fl;
                        mpf_init2(fl, 20);
-                       mpf_set_q(fl, v.num);
+                       mpf_set_q(fl, v->num);
                        gmp_printf("%Fg", fl);
                        mpf_clear(fl);
                        break;
@@ -776,61 +755,10 @@ there are errors.
                }
        }
 
-       static struct value _parse_value(struct type *type, char *arg)
-       {
-               struct value val;
-               struct text tx;
-               int neg = 0;
-               char tail[3] = "";
-
-               val.type = type;
-               switch(type->vtype) {
-               case Vlabel:                            // NOTEST
-               case Vnone:                             // NOTEST
-                       val.type = NULL;                // NOTEST
-                       break;                          // NOTEST
-               case Vstr:
-                       val.str.len = strlen(arg);
-                       val.str.txt = malloc(val.str.len);
-                       memcpy(val.str.txt, arg, val.str.len);
-                       break;
-               case Vnum:
-                       if (*arg == '-') {
-                               neg = 1;
-                               arg++;
-                       }
-                       tx.txt = arg; tx.len = strlen(tx.txt);
-                       if (number_parse(val.num, tail, tx) == 0)
-                               mpq_init(val.num);
-                       else if (neg)
-                               mpq_neg(val.num, val.num);
-                       if (tail[0]) {
-                               printf("Unsupported suffix: %s\n", arg);
-                               val.type = NULL;
-                       }
-                       break;
-               case Vbool:
-                       if (strcasecmp(arg, "true") == 0 ||
-                           strcmp(arg, "1") == 0)
-                               val.bool = 1;
-                       else if (strcasecmp(arg, "false") == 0 ||
-                                strcmp(arg, "0") == 0)
-                               val.bool = 0;
-                       else {
-                               printf("Bad bool: %s\n", arg);
-                               val.type = NULL;
-                       }
-                       break;
-               }
-               return val;
-       }
-
-       static void _free_value(struct value v);
+       static void _free_value(struct type *type, struct value *v);
 
        static struct type base_prototype = {
                .init = _val_init,
-               .prepare = _val_prepare,
-               .parse = _parse_value,
                .print = _print_value,
                .cmp_order = _value_cmp,
                .cmp_eq = _value_cmp,
@@ -841,28 +769,33 @@ there are errors.
        static struct type *Tbool, *Tstr, *Tnum, *Tnone, *Tlabel;
 
 ###### ast functions
-       static struct type *add_base_type(struct parse_context *c, char *n, enum vtype vt)
+       static struct type *add_base_type(struct parse_context *c, char *n,
+                                         enum vtype vt, int size)
        {
                struct text txt = { n, strlen(n) };
                struct type *t;
 
                t = add_type(c, txt, &base_prototype);
                t->vtype = vt;
+               t->size = size;
+               t->align = size > sizeof(void*) ? sizeof(void*) : size;
+               if (t->size & (t->align - 1))
+                       t->size = (t->size | (t->align - 1)) + 1;       // NOTEST
                return t;
        }
 
 ###### context initialization
 
-       Tbool  = add_base_type(&context, "Boolean", Vbool);
-       Tstr   = add_base_type(&context, "string", Vstr);
-       Tnum   = add_base_type(&context, "number", Vnum);
-       Tnone  = add_base_type(&context, "none", Vnone);
-       Tlabel = add_base_type(&context, "label", Vlabel);
+       Tbool  = add_base_type(&context, "Boolean", Vbool, sizeof(char));
+       Tstr   = add_base_type(&context, "string", Vstr, sizeof(struct text));
+       Tnum   = add_base_type(&context, "number", Vnum, sizeof(mpq_t));
+       Tnone  = add_base_type(&context, "none", Vnone, 0);
+       Tlabel = add_base_type(&context, "label", Vlabel, sizeof(void*));
 
 ### Variables
 
-Variables are scoped named values.  We store the names in a linked
-list of "bindings" sorted lexically, and use sequential search and
+Variables are scoped named values.  We store the names in a linked list
+of "bindings" sorted in lexical order, and use sequential search and
 insertion sort.
 
 ###### ast
@@ -912,13 +845,49 @@ cannot nest, so a declaration while a name is in-scope is an error.
 ###### ast
        struct variable {
                struct variable *previous;
-               struct value val;
+               struct type *type;
                struct binding *name;
                struct exec *where_decl;// where name was declared
                struct exec *where_set; // where type was set
                ## variable fields
        };
 
+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.
+
+####### exec fields
+       struct variable *to_free;
+
+####### variable fields
+       struct exec *cleanup_exec;
+       struct variable *next_free;
+
+####### interp exec cleanup
+       {
+               struct variable *v;
+               for (v = e->to_free; v; v = v->next_free) {
+                       struct value *val = var_value(c, v);
+                       free_value(v->type, val);
+               }
+       }
+
+###### ast functions
+       static void variable_unlink_exec(struct variable *v)
+       {
+               struct variable **vp;
+               if (!v->cleanup_exec)
+                       return;
+               for (vp = &v->cleanup_exec->to_free;
+                   *vp; vp = &(*vp)->next_free) {
+                       if (*vp != v)
+                               continue;
+                       *vp = v->next_free;
+                       v->cleanup_exec = NULL;
+                       break;
+               }
+       }
+
 While the naming seems strange, we include local constants in the
 definition of variables.  A name declared `var := value` can
 subsequently be changed, but a name declared `var ::= value` cannot -
@@ -964,12 +933,18 @@ for scoping.  When a new scope is opened, a new frame is pushed and
 the child-count of the parent frame is incremented.  This child-count
 is used to distinguish between the first of a set of parallel scopes,
 in which declared variables must not be in scope, and subsequent
-branches, whether they must already be conditionally scoped.
+branches, whether they may already be conditionally scoped.
+
+We need a total ordering of scopes so we can easily compare to variables
+to see if they are concurrently in scope.  To achieve this we record a
+`scope_count` which is actually a count of both beginnings and endings
+of scopes.  Then each variable has a record of the scope count where it
+enters scope, and where it leaves.
 
 To push a new frame *before* any code in the frame is parsed, we need a
 grammar reduction.  This is most easily achieved with a grammar
 element which derives the empty string, and creates the new scope when
-it is recognized.  This can be placed, for example, between a keyword
+it is recognised.  This can be placed, for example, between a keyword
 like "if" and the code following it.
 
 ###### ast
@@ -980,8 +955,12 @@ like "if" and the code following it.
 
 ###### parse context
        int scope_depth;
+       int scope_count;
        struct scope *scope_stack;
 
+###### variable fields
+       int scope_start, scope_end;
+
 ###### ast functions
        static void scope_pop(struct parse_context *c)
        {
@@ -990,6 +969,7 @@ like "if" and the code following it.
                c->scope_stack = s->parent;
                free(s);
                c->scope_depth -= 1;
+               c->scope_count += 1;
        }
 
        static void scope_push(struct parse_context *c)
@@ -1000,6 +980,7 @@ like "if" and the code following it.
                s->parent = c->scope_stack;
                c->scope_stack = s;
                c->scope_depth += 1;
+               c->scope_count += 1;
        }
 
 ###### Grammar
@@ -1026,17 +1007,20 @@ Each variable records a scope depth and is in one of four states:
   enclosed the declaration, and that has closed.
 
 - "conditionally in scope".  The "in scope" block and all parallel
-  scopes have closed, and no further mention of the name has been
-  seen.  This state includes a secondary nest depth which records the
-  outermost scope seen since the variable became conditionally in
-  scope.  If a use of the name is found, the variable becomes "in
-  scope" and that secondary depth becomes the recorded scope depth.
-  If the name is declared as a new variable, the old variable becomes
-  "out of scope" and the recorded scope depth stays unchanged.
+  scopes have closed, and no further mention of the name has been seen.
+  This state includes a secondary nest depth (`min_depth`) which records
+  the outermost scope seen since the variable became conditionally in
+  scope.  If a use of the name is found, the variable becomes "in scope"
+  and that secondary depth becomes the recorded scope depth.  If the
+  name is declared as a new variable, the old variable becomes "out of
+  scope" and the recorded scope depth stays unchanged.
 
 - "out of scope".  The variable is neither in scope nor conditionally
   in scope.  It is permanently out of scope now and can be removed from
-  the "in scope" stack.
+  the "in scope" stack.  When a variable becomes out-of-scope it is
+  moved to a separate list (`out_scope`) of variables which have fully
+  known scope.  This will be used at the end of each function to assign
+  each variable a place in the stack frame.
 
 ###### variable fields
        int depth, min_depth;
@@ -1046,14 +1030,14 @@ Each variable records a scope depth and is in one of four states:
 ###### parse context
 
        struct variable *in_scope;
+       struct variable *out_scope;
 
 All variables with the same name are linked together using the
-'previous' link.  Those variable that have
-been affirmatively merged all have a 'merged' pointer that points to
-one primary variable - the most recently declared instance. When
-merging variables, we need to also adjust the 'merged' pointer on any
-other variables that had previously been merged with the one that will
-no longer be primary.
+'previous' link.  Those variable that have been affirmatively merged all
+have a 'merged' pointer that points to one primary variable - the most
+recently declared instance.  When merging variables, we need to also
+adjust the 'merged' pointer on any other variables that had previously
+been merged with the one that will no longer be primary.
 
 A variable that is no longer the most recent instance of a name may
 still have "pending" scope, if it might still be merged with most
@@ -1062,6 +1046,11 @@ recent instance.  These variables don't really belong in the
 is found.  Instead, they are detected and ignored when considering the
 list of in_scope names.
 
+The storage of the value of a variable will be described later.  For now
+we just need to know that when a variable goes out of scope, it might
+need to be freed.  For this we need to be able to find it, so assume that
+`var_value()` will provide that.
+
 ###### variable fields
        struct variable *merged;
 
@@ -1071,20 +1060,26 @@ list of in_scope names.
        {
                struct variable *v;
 
-               if (primary->merged)
-                       // shouldn't happen
-                       primary = primary->merged;
+               primary = primary->merged;
 
                for (v = primary->previous; v; v=v->previous)
                        if (v == secondary || v == secondary->merged ||
                            v->merged == secondary ||
-                           (v->merged && v->merged == secondary->merged)) {
+                           v->merged == secondary->merged) {
                                v->scope = OutScope;
                                v->merged = primary;
+                               if (v->scope_start < primary->scope_start)
+                                       primary->scope_start = v->scope_start;
+                               if (v->scope_end > primary->scope_end)
+                                       primary->scope_end = v->scope_end;      // NOTEST
+                               variable_unlink_exec(v);
                        }
        }
 
-###### free context vars
+###### forward decls
+       static struct value *var_value(struct parse_context *c, struct variable *v);
+
+###### free global vars
 
        while (context.varlist) {
                struct binding *b = context.varlist;
@@ -1092,26 +1087,30 @@ list of in_scope names.
                context.varlist = b->next;
                free(b);
                while (v) {
-                       struct variable *t = v;
-
-                       v = t->previous;
-                       free_value(t->val);
-                       if (t->depth == 0)
-                               // This is a global constant
-                               free_exec(t->where_decl);
-                       free(t);
+                       struct variable *next = v->previous;
+
+                       if (v->global) {
+                               free_value(v->type, var_value(&context, v));
+                               if (v->depth == 0)
+                                       // This is a global constant
+                                       free_exec(v->where_decl);
+                       }
+                       free(v);
+                       v = next;
                }
        }
 
 #### Manipulating Bindings
 
-When a name is conditionally visible, a new declaration discards the
-old binding - the condition lapses.  Conversely a usage of the name
-affirms the visibility and extends it to the end of the containing
-block - i.e. the block that contains both the original declaration and
-the latest usage.  This is determined from `min_depth`.  When a
-conditionally visible variable gets affirmed like this, it is also
-merged with other conditionally visible variables with the same name.
+When a name is conditionally visible, a new declaration discards the old
+binding - the condition lapses.  Similarly when we reach the end of a
+function (outermost non-global scope) any conditional scope must lapse.
+Conversely a usage of the name affirms the visibility and extends it to
+the end of the containing block - i.e.  the block that contains both the
+original declaration and the latest usage.  This is determined from
+`min_depth`.  When a conditionally visible variable gets affirmed like
+this, it is also merged with other conditionally visible variables with
+the same name.
 
 When we parse a variable declaration we either report an error if the
 name is currently bound, or create a new variable at the current nest
@@ -1119,8 +1118,8 @@ depth if the name is unbound or bound to a conditionally scoped or
 pending-scope variable.  If the previous variable was conditionally
 scoped, it and its homonyms becomes out-of-scope.
 
-When we parse a variable reference (including non-declarative
-assignment) we report an error if the name is not bound or is bound to
+When we parse a variable reference (including non-declarative assignment
+"foo = bar") we report an error if the name is not bound or is bound to
 a pending-scope variable; update the scope if the name is bound to a
 conditionally scoped variable; or just proceed normally if the named
 variable is in scope.
@@ -1133,7 +1132,7 @@ switch.  Other scopes are "sequential".
 
 When exiting a parallel scope we check if there are any variables that
 were previously pending and are still visible. If there are, then
-there weren't redeclared in the most recent scope, so they cannot be
+they weren't redeclared in the most recent scope, so they cannot be
 merged and must become out-of-scope.  If it is not the first of
 parallel scopes (based on `child_count`), we check that there was a
 previous binding that is still pending-scope.  If there isn't, the new
@@ -1146,7 +1145,7 @@ we need to mark all pending-scope variable as out-of-scope.  Otherwise
 all pending-scope variables become conditionally scoped.
 
 ###### ast
-       enum closetype { CloseSequential, CloseParallel, CloseElse };
+       enum closetype { CloseSequential, CloseFunction, CloseParallel, CloseElse };
 
 ###### ast functions
 
@@ -1171,11 +1170,13 @@ all pending-scope variables become conditionally scoped.
                v->previous = b->var;
                b->var = v;
                v->name = b;
+               v->merged = v;
                v->min_depth = v->depth = c->scope_depth;
                v->scope = InScope;
                v->in_scope = c->in_scope;
+               v->scope_start = c->scope_count;
                c->in_scope = v;
-               v->val = val_prepare(NULL);
+               ## variable init
                return v;
        }
 
@@ -1207,29 +1208,71 @@ all pending-scope variables become conditionally scoped.
                return v;
        }
 
-       static void var_block_close(struct parse_context *c, enum closetype ct)
+       static int var_refile(struct parse_context *c, struct variable *v)
+       {
+               /* Variable just went out of scope.  Add it to the out_scope
+                * list, sorted by ->scope_start
+                */
+               struct variable **vp = &c->out_scope;
+               while ((*vp) && (*vp)->scope_start < v->scope_start)
+                       vp = &(*vp)->in_scope;
+               v->in_scope = *vp;
+               *vp = v;
+               return 0;               
+       }
+
+       static void var_block_close(struct parse_context *c, enum closetype ct,
+                                   struct exec *e)
        {
-               /* Close off all variables that are in_scope */
+               /* Close off all variables that are in_scope.
+                * Some variables in c->scope may already be not-in-scope,
+                * such as when a PendingScope variable is hidden by a new
+                * variable with the same name.
+                * So we check for v->name->var != v and drop them.
+                * If we choose to make a variable OutScope, we drop it
+                * immediately too.
+                */
                struct variable *v, **vp, *v2;
 
                scope_pop(c);
                for (vp = &c->in_scope;
-                    v = *vp, v && v->depth > c->scope_depth && v->min_depth > c->scope_depth;
-                    ) {
-                       if (v->name->var == v) switch (ct) {
+                    (v = *vp) && v->min_depth > c->scope_depth;
+                    (v->scope == OutScope || v->name->var != v)
+                    ? (*vp =  v->in_scope, var_refile(c, v))
+                    : ( vp = &v->in_scope, 0)) {
+                       v->min_depth = c->scope_depth;
+                       if (v->name->var != v)
+                               /* This is still in scope, but we haven't just
+                                * closed the scope.
+                                */
+                               continue;
+                       v->min_depth = c->scope_depth;
+                       if (v->scope == InScope)
+                               v->scope_end = c->scope_count;
+                       if (v->scope == InScope && e && !v->global) {
+                               /* This variable gets cleaned up when 'e' finishes */
+                               variable_unlink_exec(v);
+                               v->cleanup_exec = e;
+                               v->next_free = e->to_free;
+                               e->to_free = v;
+                       }
+                       switch (ct) {
                        case CloseElse:
                        case CloseParallel: /* handle PendingScope */
                                switch(v->scope) {
                                case InScope:
                                case CondScope:
                                        if (c->scope_stack->child_count == 1)
+                                               /* first among parallel branches */
                                                v->scope = PendingScope;
                                        else if (v->previous &&
                                                 v->previous->scope == PendingScope)
+                                               /* all previous branches used name */
                                                v->scope = PendingScope;
-                                       else if (v->val.type == Tlabel)
-                                               v->scope = PendingScope;
-                                       else if (v->name->var == v)
+                                       else 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
@@ -1241,17 +1284,25 @@ all pending-scope variables become conditionally scoped.
                                        }
                                        break;
                                case PendingScope:
-                                       for (v2 = v;
-                                            v2 && v2->scope == PendingScope;
-                                            v2 = v2->previous)
-                                               if (v2->val.type != Tlabel)
-                                                       v2->scope = OutScope;
-                                       break;
-                               case OutScope: break;
+                                       /* Not possible as it would require
+                                        * parallel scope to be nested immediately
+                                        * in a parallel scope, and that never
+                                        * happens.
+                                        */                     // NOTEST
+                               case OutScope:
+                                       /* Not possible as we already tested for
+                                        * OutScope
+                                        */
+                                       abort();                // NOTEST
                                }
                                break;
+                       case CloseFunction:
+                               if (v->scope == CondScope)
+                                       /* Condition cannot continue past end of function */
+                                       v->scope = InScope;
+                               /* fallthrough */
                        case CloseSequential:
-                               if (v->val.type == Tlabel)
+                               if (v->type == Tlabel)
                                        v->scope = PendingScope;
                                switch (v->scope) {
                                case InScope:
@@ -1266,10 +1317,9 @@ all pending-scope variables become conditionally scoped.
                                        for (v2 = v;
                                             v2 && v2->scope == PendingScope;
                                             v2 = v2->previous)
-                                               if (v2->val.type == Tlabel) {
+                                               if (v2->type == Tlabel)
                                                        v2->scope = CondScope;
-                                                       v2->min_depth = c->scope_depth;
-                                               } else
+                                               else
                                                        v2->scope = OutScope;
                                        break;
                                case CondScope:
@@ -1277,24 +1327,150 @@ all pending-scope variables become conditionally scoped.
                                }
                                break;
                        }
-                       if (v->scope == OutScope || v->name->var != v)
-                               *vp = v->in_scope;
+               }
+       }
+
+#### Storing Values
+
+The value of a variable is store separately from the variable, on an
+analogue of a stack frame.  There are (currently) two frames that can be
+active.  A global frame which currently only stores constants, and a
+stacked frame which stores local variables.  Each variable knows if it
+is global or not, and what its index into the frame is.
+
+Values in the global frame are known immediately they are relevant, so
+the frame needs to be reallocated as it grows so it can store those
+values.  The local frame doesn't get values until the interpreted phase
+is started, so there is no need to allocate until the size is known.
+
+We initialize the `frame_pos` to an impossible value, so that we can
+tell if it was set or not later.
+
+###### variable fields
+       short frame_pos;
+       short global;
+
+###### variable init
+       v->frame_pos = -1;
+
+###### parse context
+
+       short global_size, global_alloc;
+       short local_size;
+       void *global, *local;
+
+###### ast functions
+
+       static struct value *var_value(struct parse_context *c, struct variable *v)
+       {
+               if (!v->global) {
+                       if (!c->local || !v->type)
+                               return NULL;                    // NOTEST
+                       if (v->frame_pos + v->type->size > c->local_size) {
+                               printf("INVALID frame_pos\n");  // NOTEST
+                               exit(2);                        // NOTEST
+                       }
+                       return c->local + v->frame_pos;
+               }
+               if (c->global_size > c->global_alloc) {
+                       int old = c->global_alloc;
+                       c->global_alloc = (c->global_size | 1023) + 1024;
+                       c->global = realloc(c->global, c->global_alloc);
+                       memset(c->global + old, 0, c->global_alloc - old);
+               }
+               return c->global + v->frame_pos;
+       }
+
+       static struct value *global_alloc(struct parse_context *c, struct type *t,
+                                         struct variable *v, struct value *init)
+       {
+               struct value *ret;
+               struct variable scratch;
+
+               if (t->prepare_type)
+                       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);
+               if (!v) {
+                       v = &scratch;
+                       v->type = t;
+               }
+               v->frame_pos = c->global_size;
+               v->global = 1;
+               c->global_size += v->type->size;
+               ret = var_value(c, v);
+               if (init)
+                       memcpy(ret, init, t->size);
+               else
+                       val_init(t, ret);
+               return ret;
+       }
+
+As global values are found -- struct field initializers, labels etc --
+`global_alloc()` is called to record the value in the global frame.
+
+When the program is fully parsed, each function is analysed, we need to
+walk the list of variables local to that function and assign them an
+offset in the stack frame.  For this we have `scope_finalize()`.
+
+We keep the stack from dense by re-using space for between variables
+that are not in scope at the same time.  The `out_scope` list is sorted
+by `scope_start` and as we process a varible, we move it to an FIFO
+stack.  For each variable we consider, we first discard any from the
+stack anything that went out of scope before the new variable came in.
+Then we place the new variable just after the one at the top of the
+stack.
+
+###### ast functions
+
+       static void scope_finalize(struct parse_context *c, struct type *ft)
+       {
+               int size = 0;
+               struct variable *next = ft->function.scope;
+               struct variable *done = NULL;
+               while (next) {
+                       struct variable *v = next;
+                       struct type *t = v->type;
+                       int pos;
+                       next = v->in_scope;
+                       if (v->merged != v)
+                               continue;
+                       if (!t)
+                               continue;
+                       while (done && done->scope_end < v->scope_start)
+                               done = done->in_scope;
+                       if (done)
+                               pos = done->frame_pos + done->type->size;
                        else
-                               vp = &v->in_scope;
+                               pos = 0;
+                       if (pos & (t->align - 1))
+                               pos = (pos + t->align) & ~(t->align-1);
+                       v->frame_pos = pos;
+                       if (size < pos + v->type->size)
+                               size = pos + v->type->size;
+                       v->in_scope = done;
+                       done = v;
                }
+               c->out_scope = NULL;
+               ft->function.local_size = size;
        }
 
+###### free context storage
+       free(context.global);
+
 ### Executables
 
 Executables can be lots of different things.  In many cases an
 executable is just an operation combined with one or two other
-executables.  This allows for expressions and lists etc.  Other times
-an executable is something quite specific like a constant or variable
-name.  So we define a `struct exec` to be a general executable with a
-type, and a `struct binode` which is a subclass of `exec`, forms a
-node in a binary tree, and holds an operation. There will be other
-subclasses, and to access these we need to be able to `cast` the
-`exec` into the various other types.
+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.
 
 ###### macros
        #define cast(structname, pointer) ({            \
@@ -1322,6 +1498,7 @@ subclasses, and to access these we need to be able to `cast` the
        struct exec {
                enum exec_types type;
                int line, column;
+               ## exec fields
        };
        struct binode {
                struct exec;
@@ -1336,27 +1513,26 @@ subclasses, and to access these we need to be able to `cast` the
        static int __fput_loc(struct exec *loc, FILE *f)
        {
                if (!loc)
-                       return 0;               // NOTEST
+                       return 0;
                if (loc->line >= 0) {
                        fprintf(f, "%d:%d: ", loc->line, loc->column);
                        return 1;
                }
                if (loc->type == Xbinode)
                        return __fput_loc(cast(binode,loc)->left, f) ||
-                              __fput_loc(cast(binode,loc)->right, f);
+                              __fput_loc(cast(binode,loc)->right, f);  // NOTEST
                return 0;
        }
        static void fput_loc(struct exec *loc, FILE *f)
        {
                if (!__fput_loc(loc, f))
-                       fprintf(f, "??:??: ");  // NOTEST
+                       fprintf(f, "??:??: ");
        }
 
-Each different type of `exec` node needs a number of functions
-defined, a bit like methods.  We must be able to be able to free it,
-print it, analyse it and execute it.  Once we have specific `exec`
-types we will need to parse them too.  Let's take this a bit more
-slowly.
+Each different type of `exec` node needs a number of functions defined,
+a bit like methods.  We must be able to free it, print it, analyse it
+and execute it.  Once we have specific `exec` types we will need to
+parse them too.  Let's take this a bit more slowly.
 
 #### Freeing
 
@@ -1403,7 +1579,7 @@ also want to know what sort of bracketing to use.
 
        static void do_indent(int i, char *str)
        {
-               while (i--)
+               while (i-- > 0)
                        printf("    ");
                printf("%s", str);
        }
@@ -1420,12 +1596,24 @@ also want to know what sort of bracketing to use.
        static void print_exec(struct exec *e, int indent, int bracket)
        {
                if (!e)
-                       return;         // NOTEST
+                       return;
                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");
+               }
        }
 
 ###### forward decls
@@ -1434,8 +1622,8 @@ also want to know what sort of bracketing to use.
 
 #### Analysing
 
-As discussed, analysis involves propagating type requirements around
-the program and looking for errors.
+As discussed, analysis involves propagating type requirements around the
+program and looking for errors.
 
 So `propagate_types` is passed an expected type (being a `struct type`
 pointer together with some `val_rules` flags) that the `exec` is
@@ -1447,7 +1635,7 @@ propagation is needed.
 
 ###### ast
 
-       enum val_rules {Rnolabel = 1<<0, Rboolok = 1<<1, Rnoconstant = 2<<1};
+       enum val_rules {Rnolabel = 1<<0, Rboolok = 1<<1, Rnoconstant = 1<<2};
 
 ###### format cases
        case 'r':
@@ -1455,10 +1643,11 @@ propagation is needed.
                        fputs(" (labels not permitted)", stderr);
                break;
 
-###### core functions
-
+###### forward decls
        static struct type *propagate_types(struct exec *prog, struct parse_context *c, int *ok,
                                            struct type *type, int rules);
+###### core functions
+
        static struct type *__propagate_types(struct exec *prog, struct parse_context *c, int *ok,
                                              struct type *type, int rules)
        {
@@ -1495,50 +1684,85 @@ propagation is needed.
 
 Interpreting an `exec` doesn't require anything but the `exec`.  State
 is stored in variables and each variable will be directly linked from
-within the `exec` tree.  The exception to this is the whole `program`
-which needs to look at command line arguments.  The `program` will be
+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.
 
-Each `exec` can return a value, which may be `Tnone` but must be
-non-NULL;  Some `exec`s will return the location of a value, which can
-be updates.  To support this, each exec case must store either a value
-in `val` or the pointer to a value in `lval`.  If `lval` is set, but a
-simple value is required, `inter_exec()` will dereference `lval` to
-get the 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`.
 
 ###### core functions
 
        struct lrval {
-               struct value val, *lval;
+               struct type *type;
+               struct value rval, *lval;
        };
 
-       static struct lrval _interp_exec(struct exec *e);
+       /* If dest is passed, dtype must give the expected type, and
+        * result can go there, in which case type is returned as NULL.
+        */
+       static struct lrval _interp_exec(struct parse_context *c, struct exec *e,
+                                        struct value *dest, struct type *dtype);
 
-       static struct value interp_exec(struct exec *e)
+       static struct value interp_exec(struct parse_context *c, struct exec *e,
+                                       struct type **typeret)
        {
-               struct lrval ret = _interp_exec(e);
+               struct lrval ret = _interp_exec(c, e, NULL, NULL);
 
+               if (!ret.type) abort();
+               if (typeret)
+                       *typeret = ret.type;
                if (ret.lval)
-                       return dup_value(*ret.lval);
-               else
-                       return ret.val;
+                       dup_value(ret.type, ret.lval, &ret.rval);
+               return ret.rval;
        }
 
-       static struct value *linterp_exec(struct exec *e)
+       static struct value *linterp_exec(struct parse_context *c, struct exec *e,
+                                         struct type **typeret)
        {
-               struct lrval ret = _interp_exec(e);
+               struct lrval ret = _interp_exec(c, e, NULL, NULL);
 
+               if (!ret.type) abort();
+               if (ret.lval)
+                       *typeret = ret.type;
+               else
+                       free_value(ret.type, &ret.rval);
                return ret.lval;
        }
 
-       static struct lrval _interp_exec(struct exec *e)
+       /* dinterp_exec is used when the destination type is certain and
+        * the value has a place to go.
+        */
+       static void dinterp_exec(struct parse_context *c, struct exec *e,
+                                struct value *dest, struct type *dtype,
+                                int need_free)
        {
+               struct lrval ret = _interp_exec(c, e, dest, dtype);
+               if (!ret.type)
+                       return; // NOTEST
+               if (need_free)
+                       free_value(dtype, dest);
+               if (ret.lval)
+                       dup_value(dtype, ret.lval, dest);
+               else
+                       memcpy(dest, &ret.rval, dtype->size);
+       }
+
+       static struct lrval _interp_exec(struct parse_context *c, struct exec *e,
+                                        struct value *dest, struct type *dtype)
+       {
+               /* If the result is copied to dest, ret.type is set to NULL */
                struct lrval ret;
-               struct value rv, *lrv = NULL;
-               rv.type = Tnone;
+               struct value rv = {}, *lrv = NULL;
+               struct type *rvtype;
+
+               rvtype = ret.type = Tnone;
                if (!e) {
                        ret.lval = lrv;
-                       ret.val = rv;
+                       ret.rval = rv;
                        return ret;
                }
 
@@ -1547,17 +1771,23 @@ get the value.
                {
                        struct binode *b = cast(binode, e);
                        struct value left, right, *lleft;
-                       left.type = right.type = Tnone;
+                       struct type *ltype, *rtype;
+                       ltype = rtype = Tnone;
                        switch (b->op) {
                        ## interp binode cases
                        }
-                       free_value(left); free_value(right);
+                       free_value(ltype, &left);
+                       free_value(rtype, &right);
                        break;
                }
                ## interp exec cases
                }
-               ret.lval = lrv;
-               ret.val = rv;
+               if (rvtype) {
+                       ret.lval = lrv;
+                       ret.rval = rv;
+                       ret.type = rvtype;
+               }
+               ## interp exec cleanup
                return ret;
        }
 
@@ -1573,69 +1803,103 @@ Thus far we have arrays and structs.
 
 Arrays can be declared by giving a size and a type, as `[size]type' so
 `freq:[26]number` declares `freq` to be an array of 26 numbers.  The
-size can be an arbitrary expression which is evaluated when the name
-comes into scope.
+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`.
 
 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 depth.
+make a copy of an array with controllable recursive depth.
+
+For now we have two sorts of array, those with fixed size either because
+it is given as a literal number or because it is a struct member (which
+cannot have a runtime-changing size), and those with a size that is
+determined at runtime - local variables with a const size.  The former
+have their size calculated at parse time, the latter at run time.
+
+For the latter type, the `size` field of the type is the size of a
+pointer, and the array is reallocated every time it comes into scope.
+
+We differentiate struct fields with a const size from local variables
+with a const size by whether they are prepared at parse time or not.
 
 ###### type union fields
 
        struct {
-               int size;
+               int unspec;     // size is unspecified - vsize must be set.
+               short size;
+               short static_size;
                struct variable *vsize;
                struct type *member;
        } array;
 
 ###### value union fields
-       struct {
-               struct value *elmnts;
-       } array;
+       void *array;  // used if not static_size
 
 ###### value functions
 
-       static struct value array_prepare(struct type *type)
+       static void array_prepare_type(struct parse_context *c, struct type *type,
+                                      int parse_time)
        {
-               struct value ret;
+               struct value *vsize;
+               mpz_t q;
+               if (!type->array.vsize || type->array.static_size)
+                       return;
 
-               ret.type = type;
-               ret.array.elmnts = NULL;
-               return ret;
+               vsize = var_value(c, type->array.vsize);
+               mpz_init(q);
+               mpz_tdiv_q(q, mpq_numref(vsize->num), mpq_denref(vsize->num));
+               type->array.size = mpz_get_si(q);
+               mpz_clear(q);
+
+               if (parse_time) {
+                       type->array.static_size = 1;
+                       type->size = type->array.size * type->array.member->size;
+                       type->align = type->array.member->align;
+               }
        }
 
-       static struct value array_init(struct type *type)
+       static void array_init(struct type *type, struct value *val)
        {
-               struct value ret;
                int i;
-
-               ret.type = type;
-               if (type->array.vsize) {
-                       mpz_t q;
-                       mpz_init(q);
-                       mpz_tdiv_q(q, mpq_numref(type->array.vsize->val.num),
-                                  mpq_denref(type->array.vsize->val.num));
-                       type->array.size = mpz_get_si(q);
-                       mpz_clear(q);
+               void *ptr = val->ptr;
+
+               if (!val)
+                       return;                         // NOTEST
+               if (!type->array.static_size) {
+                       val->array = calloc(type->array.size,
+                                           type->array.member->size);
+                       ptr = val->array;
+               }
+               for (i = 0; i < type->array.size; i++) {
+                       struct value *v;
+                       v = (void*)ptr + i * type->array.member->size;
+                       val_init(type->array.member, v);
                }
-               ret.array.elmnts = calloc(type->array.size,
-                                         sizeof(ret.array.elmnts[0]));
-               for (i = 0; ret.array.elmnts && i < type->array.size; i++)
-                       ret.array.elmnts[i] = val_init(type->array.member);
-               return ret;
        }
 
-       static void array_free(struct value val)
+       static void array_free(struct type *type, struct value *val)
        {
                int i;
-
-               if (val.array.elmnts)
-                       for (i = 0; i < val.type->array.size; i++)
-                               free_value(val.array.elmnts[i]);
-               free(val.array.elmnts);
+               void *ptr = val->ptr;
+
+               if (!type->array.static_size)
+                       ptr = val->array;
+               for (i = 0; i < type->array.size; i++) {
+                       struct value *v;
+                       v = (void*)ptr + i * type->array.member->size;
+                       free_value(type->array.member, v);
+               }
+               if (!type->array.static_size)
+                       free(ptr);
        }
 
        static int array_compat(struct type *require, struct type *have)
@@ -1645,10 +1909,19 @@ make a copy of an array with controllable depth.
                /* 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)
+                       return 1;       // UNTESTED
                if (require->array.vsize == NULL && have->array.vsize == NULL)
                        return require->array.size == have->array.size;
 
-               return require->array.vsize == have->array.vsize;
+               return require->array.vsize == have->array.vsize;       // UNTESTED
        }
 
        static void array_print_type(struct type *type, FILE *f)
@@ -1656,36 +1929,44 @@ make a copy of an array with controllable depth.
                fputs("[", f);
                if (type->array.vsize) {
                        struct binding *b = type->array.vsize->name;
-                       fprintf(f, "%.*s]", b->name.len, b->name.txt);
+                       fprintf(f, "%.*s%s]", b->name.len, b->name.txt,
+                               type->array.unspec ? "::" : "");
                } else
                        fprintf(f, "%d]", type->array.size);
                type_print(type->array.member, f);
        }
 
        static struct type array_prototype = {
-               .prepare = array_prepare,
                .init = array_init,
+               .prepare_type = array_prepare_type,
                .print_type = array_print_type,
                .compat = array_compat,
                .free = array_free,
+               .size = sizeof(void*),
+               .align = sizeof(void*),
        };
 
+###### declare terminals
+       $TERM [ ]
+
 ###### type grammar
 
-       | [ NUMBER ] Type ${
-               $0 = calloc(1, sizeof(struct type));
-               *($0) = array_prototype;
-               $0->array.member = $<4;
-               $0->array.vsize = NULL;
-               {
+       | [ NUMBER ] Type ${ {
                char tail[3];
                mpq_t num;
+               struct text noname = { "", 0 };
+               struct type *t;
+
+               $0 = t = add_type(c, noname, &array_prototype);
+               t->array.member = $<4;
+               t->array.vsize = NULL;
                if (number_parse(num, tail, $2.txt) == 0)
                        tok_err(c, "error: unrecognised number", &$2);
-               else if (tail[0])
+               else if (tail[0]) {
                        tok_err(c, "error: unsupported number suffix", &$2);
-               else {
-                       $0->array.size = mpz_get_ui(mpq_numref(num));
+                       mpq_clear(num);
+               } else {
+                       t->array.size = mpz_get_ui(mpq_numref(num));
                        if (mpz_cmp_ui(mpq_denref(num), 1) != 0) {
                                tok_err(c, "error: array size must be an integer",
                                        &$2);
@@ -1694,40 +1975,47 @@ make a copy of an array with controllable depth.
                                        &$2);
                        mpq_clear(num);
                }
-               $0->next= c->anon_typelist;
-               c->anon_typelist = $0;
-               }
-       }$
+               t->array.static_size = 1;
+               t->size = t->array.size * t->array.member->size;
+               t->align = t->array.member->align;
+       } }$
 
        | [ IDENTIFIER ] Type ${ {
                struct variable *v = var_ref(c, $2.txt);
+               struct text noname = { "", 0 };
 
                if (!v)
                        tok_err(c, "error: name undeclared", &$2);
                else if (!v->constant)
                        tok_err(c, "error: array size must be a constant", &$2);
 
-               $0 = calloc(1, sizeof(struct type));
-               *($0) = array_prototype;
+               $0 = add_type(c, noname, &array_prototype);
                $0->array.member = $<4;
                $0->array.size = 0;
                $0->array.vsize = v;
-               $0->next= c->anon_typelist;
-               c->anon_typelist = $0;
        } }$
 
-###### parse context
-
-       struct type *anon_typelist;
+###### Grammar
+       $*type
+       OptType -> Type ${ $0 = $<1; }$
+               | ${ $0 = NULL; }$
 
-###### free context types
+###### formal type grammar
 
-       while (context.anon_typelist) {
-               struct type *t = context.anon_typelist;
+       | [ IDENTIFIER :: OptType ] Type ${ {
+               struct variable *v = var_decl(c, $ID.txt);
+               struct text noname = { "", 0 };
 
-               context.anon_typelist = t->next;
-               free(t);
-       }
+               v->type = $<OT;
+               v->constant = 1;
+               if (!v->type)
+                       v->type = Tnum;
+               $0 = add_type(c, noname, &array_prototype);
+               $0->array.member = $<6;
+               $0->array.size = 0;
+               $0->array.unspec = 1;
+               $0->array.vsize = v;
+       } }$
 
 ###### Binode types
        Index,
@@ -1773,18 +2061,25 @@ make a copy of an array with controllable depth.
        case Index: {
                mpz_t q;
                long i;
+               void *ptr;
 
-               lleft = linterp_exec(b->left);
-               right = interp_exec(b->right);
+               lleft = linterp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
                mpz_init(q);
                mpz_tdiv_q(q, mpq_numref(right.num), mpq_denref(right.num));
                i = mpz_get_si(q);
                mpz_clear(q);
 
-               if (i >= 0 && i < lleft->type->array.size)
-                       lrv = &lleft->array.elmnts[i];
+               if (ltype->array.static_size)
+                       ptr = lleft;
+               else
+                       ptr = *(void**)lleft;
+               rvtype = ltype->array.member;
+               if (i >= 0 && i < ltype->array.size)
+                       lrv = ptr + i * rvtype->size;
                else
-                       rv = val_init(lleft->type->array.member);
+                       val_init(ltype->array.member, &rv); // UNSAFE
+               ltype = NULL;
                break;
        }
 
@@ -1807,7 +2102,7 @@ is not prefixed by the word `struct` as it would be in C.
 
 Structs are only treated as the same if they have the same name.
 Simply having the same fields in the same order is not enough.  This
-might change once we can create structure initializes from a list of
+might change once we can create structure initializers from a list of
 values.
 
 Each component datum is identified much like a variable is declared,
@@ -1836,62 +2131,55 @@ function will be needed.
                struct field {
                        struct text name;
                        struct type *type;
-                       struct value init;
+                       struct value *init;
+                       int offset;
                } *fields;
        } structure;
 
-###### value union fields
-       struct {
-               struct value *fields;
-       } structure;
-
 ###### type functions
        void (*print_type_decl)(struct type *type, FILE *f);
 
 ###### value functions
 
-       static struct value structure_prepare(struct type *type)
+       static void structure_init(struct type *type, struct value *val)
        {
-               struct value ret;
-
-               ret.type = type;
-               ret.structure.fields = NULL;
-               return ret;
-       }
-
-       static struct value structure_init(struct type *type)
-       {
-               struct value ret;
                int i;
 
-               ret.type = type;
-               ret.structure.fields = calloc(type->structure.nfields,
-                                             sizeof(ret.structure.fields[0]));
-               for (i = 0; ret.structure.fields && i < type->structure.nfields; i++)
-                       ret.structure.fields[i] = val_init(type->structure.fields[i].type);
-               return ret;
+               for (i = 0; i < type->structure.nfields; i++) {
+                       struct value *v;
+                       v = (void*) val->ptr + type->structure.fields[i].offset;
+                       if (type->structure.fields[i].init)
+                               dup_value(type->structure.fields[i].type,
+                                         type->structure.fields[i].init,
+                                         v);
+                       else
+                               val_init(type->structure.fields[i].type, v);
+               }
        }
 
-       static void structure_free(struct value val)
+       static void structure_free(struct type *type, struct value *val)
        {
                int i;
 
-               if (val.structure.fields)
-                       for (i = 0; i < val.type->structure.nfields; i++)
-                               free_value(val.structure.fields[i]);
-               free(val.structure.fields);
+               for (i = 0; i < type->structure.nfields; i++) {
+                       struct value *v;
+                       v = (void*)val->ptr + type->structure.fields[i].offset;
+                       free_value(type->structure.fields[i].type, v);
+               }
        }
 
        static void structure_free_type(struct type *t)
        {
                int i;
                for (i = 0; i < t->structure.nfields; i++)
-                       free_value(t->structure.fields[i].init);
+                       if (t->structure.fields[i].init) {
+                               free_value(t->structure.fields[i].type,
+                                          t->structure.fields[i].init);
+                       }
                free(t->structure.fields);
        }
 
        static struct type structure_prototype = {
-               .prepare = structure_prepare,
                .init = structure_init,
                .free = structure_free,
                .free_type = structure_free_type,
@@ -1915,6 +2203,9 @@ function will be needed.
                free(e);
                break;
 
+###### declare terminals
+       $TERM struct .
+
 ###### variable grammar
 
        | Variable . IDENTIFIER ${ {
@@ -1953,9 +2244,9 @@ function will be needed.
                struct type *st = propagate_types(f->left, c, ok, NULL, 0);
 
                if (!st)
-                       type_err(c, "error: unknown type for field access", f->left,
+                       type_err(c, "error: unknown type for field access", f->left,    // UNTESTED
                                 NULL, 0, NULL);
-               else if (st->prepare != structure_prepare)
+               else if (st->init != structure_init)
                        type_err(c, "error: field reference attempted on %1, not a struct",
                                 f->left, st, 0, NULL);
                else if (f->index == -2) {
@@ -1978,8 +2269,10 @@ function will be needed.
        case Xfieldref:
        {
                struct fieldref *f = cast(fieldref, e);
-               struct value *lleft = linterp_exec(f->left);
-               lrv = &lleft->structure.fields[f->index];
+               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;
                break;
        }
 
@@ -1995,115 +2288,124 @@ function will be needed.
                if (!f)
                        return;
                free_fieldlist(f->prev);
-               free_value(f->f.init);
+               if (f->f.init) {
+                       free_value(f->f.type, f->f.init);       // UNTESTED
+                       free(f->f.init);        // UNTESTED
+               }
                free(f);
        }
 
 ###### top level grammar
-       DeclareStruct -> struct IDENTIFIER FieldBlock ${ {
-               struct type *t =
-                       add_type(c, $2.txt, &structure_prototype);
-               int cnt = 0;
-               struct fieldlist *f;
-
-               for (f = $3; f; f=f->prev)
-                       cnt += 1;
-
-               t->structure.nfields = cnt;
-               t->structure.fields = calloc(cnt, sizeof(struct field));
-               f = $3;
-               while (cnt > 0) {
-                       cnt -= 1;
-                       t->structure.fields[cnt] = f->f;
-                       f->f.init = val_prepare(Tnone);
-                       f = f->prev;
-               }
-       } }$
+       DeclareStruct -> struct IDENTIFIER FieldBlock Newlines ${ {
+                       struct type *t =
+                               add_type(c, $2.txt, &structure_prototype);
+                       int cnt = 0;
+                       struct fieldlist *f;
+
+                       for (f = $3; f; f=f->prev)
+                               cnt += 1;
+
+                       t->structure.nfields = cnt;
+                       t->structure.fields = calloc(cnt, sizeof(struct field));
+                       f = $3;
+                       while (cnt > 0) {
+                               int a = f->f.type->align;
+                               cnt -= 1;
+                               t->structure.fields[cnt] = f->f;
+                               if (t->size & (a-1))
+                                       t->size = (t->size | (a-1)) + 1;
+                               t->structure.fields[cnt].offset = t->size;
+                               t->size += ((f->f.type->size - 1) | (a-1)) + 1;
+                               if (a > t->align)
+                                       t->align = a;
+                               f->f.init = NULL;
+                               f = f->prev;
+                       }
+               } }$
 
-       $void
-       Open -> {
-               | NEWLINE Open
-       Close -> }
-               | NEWLINE Close
        $*fieldlist
-       FieldBlock -> Open FieldList Close ${ $0 = $<2; }$
-               | Open SimpleFieldList } ${ $0 = $<2; }$
-               | : FieldList  ${ $0 = $<2; }$
-
-       FieldList -> SimpleFieldList NEWLINE ${ $0 = $<1; }$
-               | FieldList SimpleFieldList NEWLINE ${
-                       $2->prev = $<1;
-                       $0 = $<2;
+       FieldBlock -> { IN OptNL FieldLines OUT OptNL } ${ $0 = $<FL; }$
+               | { SimpleFieldList } ${ $0 = $<SFL; }$
+               | IN OptNL FieldLines OUT ${ $0 = $<FL; }$
+               | SimpleFieldList EOL ${ $0 = $<SFL; }$
+
+       FieldLines -> SimpleFieldList Newlines ${ $0 = $<SFL; }$
+               | FieldLines SimpleFieldList Newlines ${
+                       $SFL->prev = $<FL;
+                       $0 = $<SFL;
                }$
 
-       SimpleFieldList -> Field ${ $0 = $<1; }$
+       SimpleFieldList -> Field ${ $0 = $<F; }$
                | SimpleFieldList ; Field ${
-                       $3->prev = $<1;
-                       $0 = $<3;
+                       $F->prev = $<SFL;
+                       $0 = $<F;
                }$
                | SimpleFieldList ; ${
-                       $0 = $<1;
+                       $0 = $<SFL;
                }$
+               | ERROR ${ tok_err(c, "Syntax error in struct field", &$1); }$
 
        Field -> IDENTIFIER : Type = Expression ${ {
-                       int ok;
+                       int ok; // UNTESTED
 
                        $0 = calloc(1, sizeof(struct fieldlist));
                        $0->f.name = $1.txt;
                        $0->f.type = $<3;
-                       $0->f.init = val_prepare($0->f.type);
+                       $0->f.init = NULL;
                        do {
                                ok = 1;
                                propagate_types($<5, c, &ok, $3, 0);
                        } while (ok == 2);
                        if (!ok)
-                               c->parse_error = 1;
-                       else
-                               $0->f.init = interp_exec($5);
+                               c->parse_error = 1;     // UNTESTED
+                       else {
+                               struct value vl = interp_exec(c, $5, NULL);
+                               $0->f.init = global_alloc(c, $0->f.type, NULL, &vl);
+                       }
                } }$
                | IDENTIFIER : Type ${
                        $0 = calloc(1, sizeof(struct fieldlist));
                        $0->f.name = $1.txt;
                        $0->f.type = $<3;
-                       $0->f.init = val_init($3);
+                       if ($0->f.type->prepare_type)
+                               $0->f.type->prepare_type(c, $0->f.type, 1);
                }$
-               | ERROR ${ tok_err(c, "Syntax error in struct field", &$1); }$
 
 ###### forward decls
        static void structure_print_type(struct type *t, FILE *f);
 
 ###### value functions
-       static void structure_print_type(struct type *t, FILE *f)
-       {
-               int i;
+       static void structure_print_type(struct type *t, FILE *f)       // UNTESTED
+       {       // UNTESTED
+               int i;  // UNTESTED
 
-               fprintf(f, "struct %.*s:\n", t->name.len, t->name.txt);
+               fprintf(f, "struct %.*s\n", t->name.len, t->name.txt);
 
                for (i = 0; i < t->structure.nfields; i++) {
                        struct field *fl = t->structure.fields + i;
                        fprintf(f, "    %.*s : ", fl->name.len, fl->name.txt);
                        type_print(fl->type, f);
-                       if (fl->init.type->print) {
+                       if (fl->type->print && fl->init) {
                                fprintf(f, " = ");
-                               if (fl->init.type == Tstr)
-                                       fprintf(f, "\"");
-                               print_value(fl->init);
-                               if (fl->init.type == Tstr)
-                                       fprintf(f, "\"");
+                               if (fl->type == Tstr)
+                                       fprintf(f, "\"");       // UNTESTED
+                               print_value(fl->type, fl->init);
+                               if (fl->type == Tstr)
+                                       fprintf(f, "\"");       // UNTESTED
                        }
                        printf("\n");
                }
        }
 
 ###### print type decls
-       {
-               struct type *t;
+       {       // UNTESTED
+               struct type *t; // UNTESTED
                int target = -1;
 
                while (target != 0) {
                        int i = 0;
                        for (t = context.typelist; t ; t=t->next)
-                               if (t->print_type_decl) {
+                               if (t->print_type_decl && !t->check_args) {
                                        i += 1;
                                        if (i == target)
                                                break;
@@ -2118,6 +2420,212 @@ function will be needed.
                }
        }
 
+#### Functions
+
+A function is a chunk of code which can be passed parameters and can
+return results.  Each function has a type which includes the set of
+parameters and the return value.  As yet these types cannot be declared
+separately from the function itself.
+
+The parameters can be specified either in parentheses as a ';' separated
+list, such as
+
+##### Example: function 1
+
+       func main(av:[ac::number]string; env:[envc::number]string)
+               code block
+
+or as an indented list of one parameter per line (though each line can
+be a ';' separated list)
+
+##### Example: function 2
+
+       func main
+               argv:[argc::number]string
+               env:[envc::number]string
+       do
+               code block
+
+In the first case a return type can follow the paentheses after a colon,
+in the second it is given on a line starting with the word `return`.
+
+##### Example: functions that return
+
+       func add(a:number; b:number): number
+               code block
+
+       func catenate
+               a: string
+               b: string
+       return string
+       do
+               code block
+
+
+For constructing these lists we use a `List` binode, which will be
+further detailed when Expression Lists are introduced.
+
+###### type union fields
+
+       struct {
+               struct binode *params;
+               struct type *return_type;
+               struct variable *scope;
+               int local_size;
+       } function;
+
+###### value union fields
+       struct exec *function;
+
+###### type functions
+       void (*check_args)(struct parse_context *c, int *ok,
+                          struct type *require, struct exec *args);
+
+###### value functions
+
+       static void function_free(struct type *type, struct value *val)
+       {
+               free_exec(val->function);
+               val->function = NULL;
+       }
+
+       static int function_compat(struct type *require, struct type *have)
+       {
+               // FIXME can I do anything here yet?
+               return 0;
+       }
+
+       static void function_check_args(struct parse_context *c, int *ok,
+                                       struct type *require, struct exec *args)
+       {
+               /* This should be 'compat', but we don't have a 'tuple' type to
+                * hold the type of 'args'
+                */
+               struct binode *arg = cast(binode, args);
+               struct binode *param = require->function.params;
+
+               while (param) {
+                       struct var *pv = cast(var, param->left);
+                       if (!arg) {
+                               type_err(c, "error: insufficient arguments to function.",
+                                        args, NULL, 0, NULL);
+                               break;
+                       }
+                       *ok = 1;
+                       propagate_types(arg->left, c, ok, pv->var->type, 0);
+                       param = cast(binode, param->right);
+                       arg = cast(binode, arg->right);
+               }
+               if (arg)
+                       type_err(c, "error: too many arguments to function.",
+                                args, NULL, 0, NULL);
+       }
+
+       static void function_print(struct type *type, struct value *val)
+       {
+               print_exec(val->function, 1, 0);
+       }
+
+       static void function_print_type_decl(struct type *type, FILE *f)
+       {
+               struct binode *b;
+               fprintf(f, "(");
+               for (b = type->function.params; b; b = cast(binode, b->right)) {
+                       struct variable *v = cast(var, b->left)->var;
+                       fprintf(f, "%.*s%s", v->name->name.len, v->name->name.txt,
+                               v->constant ? "::" : ":");
+                       type_print(v->type, f);
+                       if (b->right)
+                               fprintf(f, "; ");
+               }
+               fprintf(f, ")");
+               if (type->function.return_type != Tnone) {
+                       fprintf(f, ":");
+                       type_print(type->function.return_type, f);
+               }
+               fprintf(f, "\n");
+       }
+
+       static void function_free_type(struct type *t)
+       {
+               free_exec(t->function.params);
+       }
+
+       static struct type function_prototype = {
+               .size = sizeof(void*),
+               .align = sizeof(void*),
+               .free = function_free,
+               .compat = function_compat,
+               .check_args = function_check_args,
+               .print = function_print,
+               .print_type_decl = function_print_type_decl,
+               .free_type = function_free_type,
+       };
+
+###### declare terminals
+
+       $TERM func
+
+###### Binode types
+       List,
+
+###### Grammar
+
+       $*variable
+       FuncName -> IDENTIFIER ${ {
+                       struct variable *v = var_decl(c, $1.txt);
+                       struct var *e = new_pos(var, $1);
+                       e->var = v;
+                       if (v) {
+                               v->where_decl = e;
+                               $0 = v;
+                       } else {
+                               v = var_ref(c, $1.txt);
+                               e->var = v;
+                               type_err(c, "error: function '%v' redeclared",
+                                       e, NULL, 0, NULL);
+                               type_err(c, "info: this is where '%v' was first declared",
+                                       v->where_decl, NULL, 0, NULL);
+                               free_exec(e);
+                       }
+               } }$
+
+       $*binode
+       Args -> ArgsLine NEWLINE ${ $0 = $<AL; }$
+               | Args ArgsLine NEWLINE ${ {
+                       struct binode *b = $<AL;
+                       struct binode **bp = &b;
+                       while (*bp)
+                               bp = (struct binode **)&(*bp)->left;
+                       *bp = $<A;
+                       $0 = b;
+               } }$
+
+       ArgsLine -> ${ $0 = NULL; }$
+               | Varlist ${ $0 = $<1; }$
+               | Varlist ; ${ $0 = $<1; }$
+
+       Varlist -> Varlist ; ArgDecl ${
+                       $0 = new(binode);
+                       $0->op = List;
+                       $0->left = $<Vl;
+                       $0->right = $<AD;
+               }$
+               | ArgDecl ${
+                       $0 = new(binode);
+                       $0->op = List;
+                       $0->left = NULL;
+                       $0->right = $<AD;
+               }$
+
+       $*var
+       ArgDecl -> IDENTIFIER : FormalType ${ {
+               struct variable *v = var_decl(c, $1.txt);
+               $0 = new(var);
+               $0->var = v;
+               v->type = $<FT;
+       } }$
+
 ## Executables: the elements of code
 
 Each code element needs to be parsed, printed, analysed,
@@ -2137,37 +2645,44 @@ an executable.
 ###### ast
        struct val {
                struct exec;
+               struct type *vtype;
                struct value val;
        };
 
+###### ast functions
+       struct val *new_val(struct type *T, struct token tk)
+       {
+               struct val *v = new_pos(val, tk);
+               v->vtype = T;
+               return v;
+       }
+
 ###### Grammar
 
+       $TERM True False
+
        $*val
        Value ->  True ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tbool;
+                       $0 = new_val(Tbool, $1);
                        $0->val.bool = 1;
                        }$
                | False ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tbool;
+                       $0 = new_val(Tbool, $1);
                        $0->val.bool = 0;
                        }$
                | NUMBER ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tnum;
+                       $0 = new_val(Tnum, $1);
                        {
                        char tail[3];
                        if (number_parse($0->val.num, tail, $1.txt) == 0)
-                               mpq_init($0->val.num);
+                               mpq_init($0->val.num);  // UNTESTED
                                if (tail[0])
                                        tok_err(c, "error: unsupported number suffix",
                                                &$1);
                        }
                        }$
                | STRING ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tstr;
+                       $0 = new_val(Tstr, $1);
                        {
                        char tail[3];
                        string_parse(&$1, '\\', &$0->val.str, tail);
@@ -2177,8 +2692,7 @@ an executable.
                        }
                        }$
                | MULTI_STRING ${
-                       $0 = new_pos(val, $1);
-                       $0->val.type = Tstr;
+                       $0 = new_val(Tstr, $1);
                        {
                        char tail[3];
                        string_parse(&$1, '\\', &$0->val.str, tail);
@@ -2192,10 +2706,10 @@ an executable.
        case Xval:
        {
                struct val *v = cast(val, e);
-               if (v->val.type == Tstr)
+               if (v->vtype == Tstr)
                        printf("\"");
-               print_value(v->val);
-               if (v->val.type == Tstr)
+               print_value(v->vtype, &v->val);
+               if (v->vtype == Tstr)
                        printf("\"");
                break;
        }
@@ -2204,23 +2718,23 @@ an executable.
        case Xval:
        {
                struct val *val = cast(val, prog);
-               if (!type_compat(type, val->val.type, rules))
+               if (!type_compat(type, val->vtype, rules))
                        type_err(c, "error: expected %1%r found %2",
-                                  prog, type, rules, val->val.type);
-               return val->val.type;
+                                  prog, type, rules, val->vtype);
+               return val->vtype;
        }
 
 ###### interp exec cases
        case Xval:
-               rv = dup_value(cast(val, e)->val);
+               rvtype = cast(val, e)->vtype;
+               dup_value(rvtype, &cast(val, e)->val, &rv);
                break;
 
 ###### ast functions
        static void free_val(struct val *v)
        {
-               if (!v)
-                       return;
-               free_value(v->val);
+               if (v)
+                       free_value(v->vtype, &v->val);
                free(v);
        }
 
@@ -2228,7 +2742,7 @@ an executable.
        case Xval: free_val(cast(val, e)); break;
 
 ###### ast functions
-       // Move all nodes from 'b' to 'rv', reversing the order.
+       // 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)
@@ -2252,7 +2766,7 @@ an executable.
 
 Just as we used a `val` to wrap a value into an `exec`, we similarly
 need a `var` to wrap a `variable` into an exec.  While each `val`
-contained a copy of the value, each `var` hold a link to the variable
+contained a copy of the value, each `var` holds a link to the variable
 because it really is the same variable no matter where it appears.
 When a variable is used, we need to remember to follow the `->merged`
 link to find the primary instance.
@@ -2268,6 +2782,8 @@ link to find the primary instance.
 
 ###### Grammar
 
+       $TERM : ::
+
        $*var
        VariableDecl -> IDENTIFIER : ${ {
                struct variable *v = var_decl(c, $1.txt);
@@ -2307,7 +2823,7 @@ link to find the primary instance.
                if (v) {
                        v->where_decl = $0;
                        v->where_set = $0;
-                       v->val = val_prepare($<3);
+                       v->type = $<Type;
                } else {
                        v = var_ref(c, $1.txt);
                        $0->var = v;
@@ -2324,7 +2840,7 @@ link to find the primary instance.
                if (v) {
                        v->where_decl = $0;
                        v->where_set = $0;
-                       v->val = val_prepare($<3);
+                       v->type = $<Type;
                        v->constant = 1;
                } else {
                        v = var_ref(c, $1.txt);
@@ -2344,7 +2860,7 @@ link to find the primary instance.
                        /* This might be a label - allocate a var just in case */
                        v = var_decl(c, $1.txt);
                        if (v) {
-                               v->val = val_prepare(Tnone);
+                               v->type = Tnone;
                                v->where_decl = $0;
                                v->where_set = $0;
                        }
@@ -2353,18 +2869,6 @@ link to find the primary instance.
        } }$
        ## variable grammar
 
-       $*type
-       Type -> IDENTIFIER ${
-               $0 = find_type(c, $1.txt);
-               if (!$0) {
-                       tok_err(c,
-                               "error: undefined type", &$1);
-
-                       $0 = Tnone;
-               }
-       }$
-       ## type grammar
-
 ###### print exec cases
        case Xvar:
        {
@@ -2378,7 +2882,7 @@ link to find the primary instance.
 
 ###### format cases
        case 'v':
-               if (loc->type == Xvar) {
+               if (loc && loc->type == Xvar) {
                        struct var *v = cast(var, loc);
                        if (v->var) {
                                struct binding *b = v->var->name;
@@ -2386,7 +2890,7 @@ link to find the primary instance.
                        } else
                                fputs("???", stderr);   // NOTEST
                } else
-                       fputs("NOTVAR", stderr);        // NOTEST
+                       fputs("NOTVAR", stderr);
                break;
 
 ###### propagate exec cases
@@ -2399,34 +2903,33 @@ link to find the primary instance.
                        type_err(c, "%d:BUG: no variable!!", prog, NULL, 0, NULL); // NOTEST
                        return Tnone;                                   // NOTEST
                }
-               if (v->merged)
-                       v = v->merged;
+               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->val.type;
+                       return v->type;
                }
-               if (v->val.type == Tnone && v->where_decl == prog)
+               if (v->type == Tnone && v->where_decl == prog)
                        type_err(c, "error: variable used but not declared: %v",
                                 prog, NULL, 0, NULL);
-               if (v->val.type == NULL) {
+               if (v->type == NULL) {
                        if (type && *ok != 0) {
-                               v->val = val_prepare(type);
+                               v->type = type;
                                v->where_set = prog;
                                *ok = 2;
                        }
                        return type;
                }
-               if (!type_compat(type, v->val.type, rules)) {
+               if (!type_compat(type, v->type, rules)) {
                        type_err(c, "error: expected %1%r but variable '%v' is %2", prog,
-                                type, rules, v->val.type);
+                                type, rules, v->type);
                        type_err(c, "info: this is where '%v' was set to %1", v->where_set,
-                                v->val.type, rules, NULL);
+                                v->type, rules, NULL);
                }
                if (!type)
-                       return v->val.type;
+                       return v->type;
                return type;
        }
 
@@ -2436,9 +2939,9 @@ link to find the primary instance.
                struct var *var = cast(var, e);
                struct variable *v = var->var;
 
-               if (v->merged)
-                       v = v->merged;
-               lrv = &v->val;
+               v = v->merged;
+               lrv = var_value(c, v);
+               rvtype = v->type;
                break;
        }
 
@@ -2457,15 +2960,15 @@ link to find the primary instance.
 Our first user of the `binode` will be conditional expressions, which
 is a bit odd as they actually have three components.  That will be
 handled by having 2 binodes for each expression.  The conditional
-expression is the lowest precedence operatior, so it gets to define
-what an "Expression" is.  The next level up is "BoolExpr", which
-comes next.
+expression is the lowest precedence operator which is why we define it
+first - to start the precedence list.
 
 Conditional expressions are of the form "value `if` condition `else`
 other_value".  They associate to the right, so everything to the right
-of `else` is part of an else value, while only the BoolExpr to the
-left of `if` is the if values.  Between `if` and `else` there is no
-room for ambiguity, so a full conditional expression is allowed in there.
+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
+room for ambiguity, so a full conditional expression is allowed in
+there.
 
 ###### Binode types
        CondExpr,
@@ -2519,24 +3022,71 @@ room for ambiguity, so a full conditional expression is allowed in there.
 
        case CondExpr: {
                struct binode *b2 = cast(binode, b->right);
-               left = interp_exec(b->left);
+               left = interp_exec(c, b->left, &ltype);
                if (left.bool)
-                       rv = interp_exec(b2->left);
+                       rv = interp_exec(c, b2->left, &rvtype); // UNTESTED
                else
-                       rv = interp_exec(b2->right);
+                       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.  As I haven't implemented precedence in the parser
-generator yet, we need different names for each precedence level used
-by expressions.  The outer most or lowest level precedence after
-conditional expressions are Boolean operators which form an `BoolExpr`
-out of `BTerm`s and `BFact`s.  As well as `or` `and`, and `not` we
-have `and then` and `or else` which only evaluate the second operand
-if the result would make a difference.
+expressions.  "`and then`" and "`or else`" are similar to `and` and `or`
+have same corresponding precendence.  The difference is that they don't
+evaluate the second expression if not necessary.
 
 ###### Binode types
        And,
@@ -2640,39 +3190,38 @@ if the result would make a difference.
 
 ###### interp binode cases
        case And:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               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(b->left);
+               rv = interp_exec(c, b->left, &rvtype);
                if (rv.bool)
-                       rv = interp_exec(b->right);
+                       rv = interp_exec(c, b->right, NULL);
                break;
        case Or:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               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(b->left);
+               rv = interp_exec(c, b->left, &rvtype);
                if (!rv.bool)
-                       rv = interp_exec(b->right);
+                       rv = interp_exec(c, b->right, NULL);
                break;
        case Not:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                rv.bool = !rv.bool;
                break;
 
 ### Expressions: Comparison
 
-Of slightly higher precedence that Boolean expressions are
-Comparisons.
-A comparison takes arguments of any comparable type, but the two types must be
-the same.
+Of slightly higher precedence that Boolean expressions are Comparisons.
+A comparison takes arguments of any comparable type, but the two types
+must be the same.
 
 To simplify the parsing we introduce an `eop` which can record an
-expression operator.
+expression operator, and the `CMPop` non-terminal will match one of them.
 
 ###### ast
        struct eop {
@@ -2751,9 +3300,9 @@ expression operator.
                if (t)
                        propagate_types(b->right, c, ok, t, 0);
                else {
-                       t = propagate_types(b->right, c, ok, NULL, Rnolabel);
-                       if (t)
-                               t = propagate_types(b->left, c, ok, t, 0);
+                       t = propagate_types(b->right, c, ok, NULL, Rnolabel);   // UNTESTED
+                       if (t)  // UNTESTED
+                               t = propagate_types(b->left, c, ok, t, 0);      // UNTESTED
                }
                if (!type_compat(type, Tbool, 0))
                        type_err(c, "error: Comparison returns %1 but %2 expected", prog,
@@ -2769,10 +3318,10 @@ expression operator.
        case NEql:
        {
                int cmp;
-               left = interp_exec(b->left);
-               right = interp_exec(b->right);
-               cmp = value_cmp(left, right);
-               rv.type = Tbool;
+               left = interp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               cmp = value_cmp(ltype, rtype, &left, &right);
+               rvtype = Tbool;
                switch (b->op) {
                case Less:      rv.bool = cmp <  0; break;
                case LessEq:    rv.bool = cmp <= 0; break;
@@ -2780,36 +3329,42 @@ expression operator.
                case GtrEq:     rv.bool = cmp >= 0; break;
                case Eql:       rv.bool = cmp == 0; break;
                case NEql:      rv.bool = cmp != 0; break;
-               default: rv.bool = 0; break;    // NOTEST
+               default:        rv.bool = 0; break;     // NOTEST
                }
                break;
        }
 
-### Expressions: The rest
+### Expressions: Arithmetic etc.
 
-The remaining expressions with the highest precedence are arithmetic
-and string concatenation.  They are `Expr`, `Term`, and `Factor`.
-The `Factor` is where the `Value` and `Variable` that we already have
-are included.
+The remaining expressions with the highest precedence are arithmetic,
+string concatenation, and string conversion.  String concatenation
+(`++`) has the same precedence as multiplication and division, but lower
+than the uniary.
+
+String conversion is a temporary feature until I get a better type
+system.  `$` is a prefix operator which expects a string and returns
+a number.
 
 `+` and `-` are both infix and prefix operations (where they are
 absolute value and negation).  These have different operator names.
 
 We also have a 'Bracket' operator which records where parentheses were
-found.  This makes it easy to reproduce these when printing.  Once
-precedence is handled better I might be able to discard this.
+found.  This makes it easy to reproduce these when printing.  Possibly I
+should only insert brackets were needed for precedence.
 
 ###### Binode types
        Plus, Minus,
        Times, Divide, Rem,
        Concat,
        Absolute, Negate,
+       StringConv,
        Bracket,
 
 ###### expr precedence
        $LEFT + - Eop
        $LEFT * / % ++ Top
-       $LEFT Uop
+       $LEFT Uop $
+       $TERM ( )
 
 ###### expression grammar
                | Expression Eop Expression ${ {
@@ -2843,12 +3398,15 @@ precedence is handled better I might be able to discard this.
                | Value ${ $0 = $<1; }$
                | Variable ${ $0 = $<1; }$
 
+###### Grammar
+
        $eop
        Eop ->    + ${ $0.op = Plus; }$
                | - ${ $0.op = Minus; }$
 
        Uop ->    + ${ $0.op = Absolute; }$
                | - ${ $0.op = Negate; }$
+               | $ ${ $0.op = StringConv; }$
 
        Top ->    * ${ $0.op = Times; }$
                | / ${ $0.op = Divide; }$
@@ -2877,14 +3435,15 @@ precedence is handled better I might be able to discard this.
                if (bracket) printf(")");
                break;
        case Absolute:
-               if (bracket) printf("(");
-               printf("+");
-               print_exec(b->right, indent, bracket);
-               if (bracket) printf(")");
-               break;
        case Negate:
+       case StringConv:
                if (bracket) printf("(");
-               printf("-");
+               switch (b->op) {
+               case Absolute:   fputs("+", stdout); break;
+               case Negate:     fputs("-", stdout); break;
+               case StringConv: fputs("$", stdout); break;
+               default: abort();       // NOTEST
+               }                       // NOTEST
                print_exec(b->right, indent, bracket);
                if (bracket) printf(")");
                break;
@@ -2921,62 +3480,93 @@ precedence is handled better I might be able to discard this.
                                   Tstr, rules, type);
                return Tstr;
 
+       case StringConv:
+               /* op must be string, result is number */
+               propagate_types(b->left, c, ok, Tstr, 0);
+               if (!type_compat(type, Tnum, 0))
+                       type_err(c,     // UNTESTED
+                         "error: Can only convert string to number, not %1",
+                               prog, type, 0, NULL);
+               return Tnum;
+
        case Bracket:
                return propagate_types(b->right, c, ok, type, 0);
 
 ###### interp binode cases
 
        case Plus:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_add(rv.num, rv.num, right.num);
                break;
        case Minus:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_sub(rv.num, rv.num, right.num);
                break;
        case Times:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_mul(rv.num, rv.num, right.num);
                break;
        case Divide:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_div(rv.num, rv.num, right.num);
                break;
        case Rem: {
                mpz_t l, r, rem;
 
-               left = interp_exec(b->left);
-               right = interp_exec(b->right);
+               left = interp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
                mpz_init(l); mpz_init(r); mpz_init(rem);
                mpz_tdiv_q(l, mpq_numref(left.num), mpq_denref(left.num));
                mpz_tdiv_q(r, mpq_numref(right.num), mpq_denref(right.num));
                mpz_tdiv_r(rem, l, r);
-               rv = val_init(Tnum);
+               val_init(Tnum, &rv);
                mpq_set_z(rv.num, rem);
                mpz_clear(r); mpz_clear(l); mpz_clear(rem);
+               rvtype = ltype;
                break;
        }
        case Negate:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                mpq_neg(rv.num, rv.num);
                break;
        case Absolute:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                mpq_abs(rv.num, rv.num);
                break;
        case Bracket:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                break;
        case Concat:
-               left = interp_exec(b->left);
-               right = interp_exec(b->right);
-               rv.type = Tstr;
+               left = interp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               rvtype = Tstr;
                rv.str = text_join(left.str, right.str);
                break;
+       case StringConv:
+               right = interp_exec(c, b->right, &rvtype);
+               rtype = Tstr;
+               rvtype = Tnum;
+
+               struct text tx = right.str;
+               char tail[3];
+               int neg = 0;
+               if (tx.txt[0] == '-') {
+                       neg = 1;        // UNTESTED
+                       tx.txt++;       // UNTESTED
+                       tx.len--;       // UNTESTED
+               }
+               if (number_parse(rv.num, tail, tx) == 0)
+                       mpq_init(rv.num);       // UNTESTED
+               else if (neg)
+                       mpq_neg(rv.num, rv.num);        // UNTESTED
+               if (tail[0])
+                       printf("Unsupported suffix: %.*s\n", tx.len, tx.txt);   // UNTESTED
+
+               break;
 
 ###### value functions
 
@@ -2990,6 +3580,110 @@ precedence is handled better I might be able to discard this.
                return rv;
        }
 
+### Function calls
+
+A function call can appear either as an expression or as a statement.
+As functions cannot yet return values, only the statement version will work.
+We use a new 'Funcall' binode type to link the function with a list of
+arguments, form with the 'List' nodes.
+
+###### Binode types
+       Funcall,
+
+###### expression grammar
+       | Variable ( ExpressionList ) ${ {
+               struct binode *b = new(binode);
+               b->op = Funcall;
+               b->left = $<V;
+               b->right = reorder_bilist($<EL);
+               $0 = b;
+       } }$
+       | Variable ( ) ${ {
+               struct binode *b = new(binode);
+               b->op = Funcall;
+               b->left = $<V;
+               b->right = NULL;
+               $0 = b;
+       } }$
+
+###### SimpleStatement Grammar
+
+       | Variable ( ExpressionList ) ${ {
+               struct binode *b = new(binode);
+               b->op = Funcall;
+               b->left = $<V;
+               b->right = reorder_bilist($<EL);
+               $0 = b;
+       } }$
+
+###### print binode cases
+
+       case Funcall:
+               do_indent(indent, "");
+               print_exec(b->left, -1, bracket);
+               printf("(");
+               for (b = cast(binode, b->right); b; b = cast(binode, b->right)) {
+                       if (b->left) {
+                               printf(" ");
+                               print_exec(b->left, -1, bracket);
+                               if (b->right)
+                                       printf(",");
+                       }
+               }
+               printf(")");
+               if (indent >= 0)
+                       printf("\n");
+               break;
+
+###### propagate binode cases
+
+       case Funcall: {
+               /* Every arg must match formal parameter, and result
+                * is return type of function
+                */
+               struct binode *args = cast(binode, b->right);
+               struct var *v = cast(var, b->left);
+
+               if (!v->var->type || v->var->type->check_args == NULL) {
+                       type_err(c, "error: attempt to call a non-function.",
+                                prog, NULL, 0, NULL);
+                       return NULL;
+               }
+               v->var->type->check_args(c, ok, v->var->type, args);
+               return v->var->type->function.return_type;
+       }
+
+###### interp binode cases
+
+       case Funcall: {
+               struct var *v = cast(var, b->left);
+               struct type *t = v->var->type;
+               void *oldlocal = c->local;
+               int old_size = c->local_size;
+               void *local = calloc(1, t->function.local_size);
+               struct value *fbody = var_value(c, v->var);
+               struct binode *arg = cast(binode, b->right);
+               struct binode *param = t->function.params;
+
+               while (param) {
+                       struct var *pv = cast(var, param->left);
+                       struct type *vtype = NULL;
+                       struct value val = interp_exec(c, arg->left, &vtype);
+                       struct value *lval;
+                       c->local = local; c->local_size = t->function.local_size;
+                       lval = var_value(c, pv->var);
+                       c->local = oldlocal; c->local_size = old_size;
+                       memcpy(lval, &val, vtype->size);
+                       param = cast(binode, param->right);
+                       arg = cast(binode, arg->right);
+               }
+               c->local = local; c->local_size = t->function.local_size;
+               rv = interp_exec(c, fbody->function, &rvtype);
+               c->local = oldlocal; c->local_size = old_size;
+               free(local);
+               break;
+       }
+
 ### Blocks, Statements, and Statement lists.
 
 Now that we have expressions out of the way we need to turn to
@@ -3018,7 +3712,7 @@ confusion, so I'm not set on it yet.
 
 A simple statement list needs no extra syntax.  A complex statement
 list has two syntactic forms.  It can be enclosed in braces (much like
-C blocks), or it can be introduced by a colon and continue until an
+C blocks), or it can be introduced by an indent and continue until an
 unindented newline (much like Python blocks).  With this extra syntax
 it is referred to as a block.
 
@@ -3048,17 +3742,32 @@ is in-place.
 
 ###### Grammar
 
-       $void
-       Newlines -> NEWLINE
-               | Newlines NEWLINE
+       $TERM { } ;
 
        $*binode
-       Block -> Open Statementlist Close ${ $0 = $<2; }$
-               | Open SimpleStatements } ${ $0 = reorder_bilist($<2); }$
-               | : SimpleStatements ${ $0 = reorder_bilist($<2); }$
-               | : Statementlist  ${ $0 = $<2; }$
-
-       Statementlist -> ComplexStatements ${ $0 = reorder_bilist($<1); }$
+       Block -> { IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+               | { SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+               | SimpleStatements ; ${ $0 = reorder_bilist($<SS); }$
+               | SimpleStatements EOL ${ $0 = reorder_bilist($<SS); }$
+               | IN OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       OpenBlock -> OpenScope { IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+               | OpenScope { SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+               | OpenScope SimpleStatements ; ${ $0 = reorder_bilist($<SS); }$
+               | OpenScope SimpleStatements EOL ${ $0 = reorder_bilist($<SS); }$
+               | IN OpenScope OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       UseBlock -> { OpenScope IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+               | { OpenScope SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+               | IN OpenScope OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       ColonBlock -> { IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+               | { SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+               | : SimpleStatements ; ${ $0 = reorder_bilist($<SS); }$
+               | : SimpleStatements EOL ${ $0 = reorder_bilist($<SS); }$
+               | : IN OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       Statementlist -> ComplexStatements ${ $0 = reorder_bilist($<CS); }$
 
        ComplexStatements -> ComplexStatements ComplexStatement ${
                        if ($2 == NULL) {
@@ -3082,10 +3791,12 @@ is in-place.
                }$
 
        $*exec
-       ComplexStatement -> SimpleStatements NEWLINE ${
-                       $0 = reorder_bilist($<1);
+       ComplexStatement -> SimpleStatements Newlines ${
+                       $0 = reorder_bilist($<SS);
+                       }$
+               |  SimpleStatements ; Newlines ${
+                       $0 = reorder_bilist($<SS);
                        }$
-               | Newlines ${ $0 = NULL; }$
                ## ComplexStatement Grammar
 
        $*binode
@@ -3101,8 +3812,8 @@ is in-place.
                        $0->left = NULL;
                        $0->right = $<1;
                        }$
-               | SimpleStatements ; ${ $0 = $<1; }$
 
+       $TERM pass
        SimpleStatement -> pass ${ $0 = NULL; }$
                | ERROR ${ tok_err(c, "Syntax error in statement", &$1); }$
                ## SimpleStatement Grammar
@@ -3111,13 +3822,13 @@ is in-place.
        case Block:
                if (indent < 0) {
                        // simple statement
-                       if (b->left == NULL)
-                               printf("pass");
+                       if (b->left == NULL)    // UNTESTED
+                               printf("pass"); // UNTESTED
                        else
-                               print_exec(b->left, indent, bracket);
-                       if (b->right) {
-                               printf("; ");
-                               print_exec(b->right, indent, bracket);
+                               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
@@ -3144,9 +3855,14 @@ is in-place.
 
                for (e = b; e; e = cast(binode, e->right)) {
                        t = propagate_types(e->left, c, ok, NULL, rules);
-                       if ((rules & Rboolok) && t == Tbool)
+                       if ((rules & Rboolok) && (t == Tbool || t == Tnone))
                                t = NULL;
-                       if (t && t != Tnone && t != Tbool) {
+                       if (t == Tnone && e->right)
+                               /* Only the final statement *must* return a value
+                                * when not Rboolok
+                                */
+                               t = NULL;
+                       if (t) {
                                if (!type)
                                        type = t;
                                else if (t != type)
@@ -3159,10 +3875,10 @@ is in-place.
 
 ###### interp binode cases
        case Block:
-               while (rv.type == Tnone &&
+               while (rvtype == Tnone &&
                       b) {
                        if (b->left)
-                               rv = interp_exec(b->left);
+                               rv = interp_exec(c, b->left, &rvtype);
                        b = cast(binode, b->right);
                }
                break;
@@ -3173,59 +3889,47 @@ is in-place.
 expressions and prints the values separated by spaces and terminated
 by a newline.  No control of formatting is possible.
 
-`print` faces the same list-ordering issue as blocks, and uses the
-same solution.
+`print` uses `ExpressionList` to collect the expressions and stores them
+on the left side of a `Print` binode unlessthere is a trailing comma
+when the list is stored on the `right` side and no trailing newline is
+printed.
 
 ###### Binode types
        Print,
 
+##### expr precedence
+       $TERM print
+
 ###### SimpleStatement Grammar
 
        | print ExpressionList ${
-               $0 = reorder_bilist($<2);
-       }$
-       | print ExpressionList , ${
                $0 = new(binode);
                $0->op = Print;
                $0->right = NULL;
-               $0->left = $<2;
-               $0 = reorder_bilist($0);
+               $0->left = reorder_bilist($<EL);
        }$
+       | print ExpressionList , ${ {
+               $0 = new(binode);
+               $0->op = Print;
+               $0->right = reorder_bilist($<EL);
+               $0->left = NULL;
+       } }$
        | print ${
                $0 = new(binode);
                $0->op = Print;
+               $0->left = NULL;
                $0->right = NULL;
        }$
 
-###### Grammar
-
-       $*binode
-       ExpressionList -> ExpressionList , Expression ${
-               $0 = new(binode);
-               $0->op = Print;
-               $0->left = $<1;
-               $0->right = $<3;
-               }$
-               | Expression ${
-                       $0 = new(binode);
-                       $0->op = Print;
-                       $0->left = NULL;
-                       $0->right = $<1;
-               }$
-
 ###### print binode cases
 
        case Print:
                do_indent(indent, "print");
-               while (b) {
-                       if (b->left) {
-                               printf(" ");
-                               print_exec(b->left, -1, bracket);
-                               if (b->right)
-                                       printf(",");
-                       }
-                       b = cast(binode, b->right);
-               }
+               if (b->right) {
+                       print_exec(b->right, -1, bracket);
+                       printf(",");
+               } else
+                       print_exec(b->left, -1, bracket);
                if (indent >= 0)
                        printf("\n");
                break;
@@ -3234,37 +3938,40 @@ same solution.
 
        case Print:
                /* don't care but all must be consistent */
-               propagate_types(b->left, c, ok, NULL, Rnolabel);
-               propagate_types(b->right, c, ok, NULL, Rnolabel);
+               if (b->left)
+                       b = cast(binode, b->left);
+               else
+                       b = cast(binode, b->right);
+               while (b) {
+                       propagate_types(b->left, c, ok, NULL, Rnolabel);
+                       b = cast(binode, b->right);
+               }
                break;
 
 ###### interp binode cases
 
        case Print:
        {
-               char sep = 0;
-               int eol = 1;
-               for ( ; b; b = cast(binode, b->right))
-                       if (b->left) {
-                               if (sep)
-                                       putchar(sep);
-                               left = interp_exec(b->left);
-                               print_value(left);
-                               free_value(left);
-                               if (b->right)
-                                       sep = ' ';
-                       } else if (sep)
-                               eol = 0;
-               left.type = Tnone;
-               if (eol)
+               struct binode *b2 = cast(binode, b->left);
+               if (!b2)
+                       b2 = cast(binode, b->right);
+               for (; b2; b2 = cast(binode, b2->right)) {
+                       left = interp_exec(c, b2->left, &ltype);
+                       print_value(ltype, &left);
+                       free_value(ltype, &left);
+                       if (b2->right)
+                               putchar(' ');
+               }
+               if (b->right == NULL)
                        printf("\n");
+               ltype = Tnone;
                break;
        }
 
 ###### Assignment statement
 
 An assignment will assign a value to a variable, providing it hasn't
-be declared as a constant.  The analysis phase ensures that the type
+been declared as a constant.  The analysis phase ensures that the type
 will be correct so the interpreter just needs to perform the
 calculation.  There is a form of assignment which declares a new
 variable as well as assigning a value.  If a name is assigned before
@@ -3275,6 +3982,9 @@ it is declared, and error will be raised as the name is created as
        Assign,
        Declare,
 
+###### declare terminals
+       $TERM =
+
 ###### SimpleStatement Grammar
        | Variable = Expression ${
                        $0 = new(binode);
@@ -3294,6 +4004,7 @@ it is declared, and error will be raised as the name is created as
                                type_err(c,
                                         "Variable declared with no type or value: %v",
                                         $1, NULL, 0, NULL);
+                               free_var($1);
                        } else {
                                $0 = new(binode);
                                $0->op = Declare;
@@ -3319,19 +4030,17 @@ it is declared, and error will be raised as the name is created as
                do_indent(indent, "");
                print_exec(b->left, indent, bracket);
                if (cast(var, b->left)->var->constant) {
+                       printf("::");
                        if (v->where_decl == v->where_set) {
-                               printf("::");
-                               type_print(v->val.type, stdout);
+                               type_print(v->type, stdout);
                                printf(" ");
-                       } else
-                               printf(" ::");
+                       }
                } else {
+                       printf(":");
                        if (v->where_decl == v->where_set) {
-                               printf(":");
-                               type_print(v->val.type, stdout);
+                               type_print(v->type, stdout);
                                printf(" ");
-                       } else
-                               printf(" :");
+                       }
                }
                if (b->right) {
                        printf("= ");
@@ -3376,41 +4085,40 @@ it is declared, and error will be raised as the name is created as
 ###### interp binode cases
 
        case Assign:
-               lleft = linterp_exec(b->left);
-               right = interp_exec(b->right);
-               if (lleft) {
-                       free_value(*lleft);
-                       *lleft = right;
-               } else
-                       free_value(right);      // NOTEST
-               right.type = NULL;
+               lleft = linterp_exec(c, b->left, &ltype);
+               if (lleft)
+                       dinterp_exec(c, b->right, lleft, ltype, 1);
+               ltype = Tnone;
                break;
 
        case Declare:
        {
                struct variable *v = cast(var, b->left)->var;
-               if (v->merged)
-                       v = v->merged;
+               struct value *val;
+               v = v->merged;
+               val = var_value(c, v);
+               if (v->type->prepare_type)
+                       v->type->prepare_type(c, v->type, 0);
                if (b->right)
-                       right = interp_exec(b->right);
+                       dinterp_exec(c, b->right, val, v->type, 0);
                else
-                       right = val_init(v->val.type);
-               free_value(v->val);
-               v->val = right;
-               right.type = NULL;
+                       val_init(v->type, val);
                break;
        }
 
 ### The `use` statement
 
-The `use` statement is the last "simple" statement.  It is needed when
-the condition in a conditional statement is a block.  `use` works much
-like `return` in C, but only completes the `condition`, not the whole
-function.
+The `use` statement is the last "simple" statement.  It is needed when a
+statement block can return a value.  This includes the body of a
+function which has a return type, and the "condition" code blocks in
+`if`, `while`, and `switch` statements.
 
 ###### Binode types
        Use,
 
+###### expr precedence
+       $TERM use
+
 ###### SimpleStatement Grammar
        | use Expression ${
                $0 = new_pos(binode, $1);
@@ -3418,10 +4126,13 @@ function.
                $0->right = $<2;
                if ($0->right->type == Xvar) {
                        struct var *v = cast(var, $0->right);
-                       if (v->var->val.type == Tnone) {
+                       if (v->var->type == Tnone) {
                                /* Convert this to a label */
-                               v->var->val = val_prepare(Tlabel);
-                               v->var->val.label = &v->var->val;
+                               struct value *val;
+
+                               v->var->type = Tlabel;
+                               val = global_alloc(c, Tlabel, v->var, NULL);
+                               val->label = val;
                        }
                }
        }$
@@ -3444,7 +4155,7 @@ function.
 ###### interp binode cases
 
        case Use:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                break;
 
 ### The Conditional Statement
@@ -3509,7 +4220,15 @@ the type of the `whilepart` code block is the reason for the
 `Rboolok` flag which is passed to `propagate_types()`.
 
 The `cond_statement` cannot fit into a `binode` so a new `exec` is
-defined.
+defined.  As there are two scopes which cover multiple parts - one for
+the whole statement and one for "while" and "do" - and as we will use
+the 'struct exec' to track scopes, we actually need two new types of
+exec.  One is a `binode` for the looping part, the rest is the
+`cond_statement`.  The `cond_statement` will use an auxilliary `struct
+casepart` to track a list of case parts.
+
+###### Binode types
+       Loop
 
 ###### exec type
        Xcond_statement,
@@ -3522,7 +4241,8 @@ defined.
        };
        struct cond_statement {
                struct exec;
-               struct exec *forpart, *condpart, *dopart, *thenpart, *elsepart;
+               struct exec *forpart, *condpart, *thenpart, *elsepart;
+               struct binode *looppart;
                struct casepart *casepart;
        };
 
@@ -3546,7 +4266,7 @@ defined.
                        return;
                free_exec(s->forpart);
                free_exec(s->condpart);
-               free_exec(s->dopart);
+               free_exec(s->looppart);
                free_exec(s->thenpart);
                free_exec(s->elsepart);
                free_casepart(s->casepart);
@@ -3559,159 +4279,183 @@ defined.
 ###### ComplexStatement Grammar
        | CondStatement ${ $0 = $<1; }$
 
+###### expr precedence
+       $TERM for then while do
+       $TERM else
+       $TERM switch case
+
 ###### Grammar
 
        $*cond_statement
-       // both ForThen and Whilepart open scopes, and CondSuffix only
-       // closes one - so in the first branch here we have another to close.
-       CondStatement -> forPart ThenPart WhilePart CondSuffix ${
-                       $0 = $<4;
-                       $0->forpart = $<1;
-                       $0->thenpart = $<2;
-                       $0->condpart = $3.condpart; $3.condpart = NULL;
-                       $0->dopart = $3.dopart; $3.dopart = NULL;
-                       var_block_close(c, CloseSequential);
+       // A CondStatement must end with EOL, as does CondSuffix and
+       // IfSuffix.
+       // ForPart, ThenPart, SwitchPart, CasePart are non-empty and
+       // may or may not end with EOL
+       // WhilePart and IfPart include an appropriate Suffix
+
+       // ForPart, SwitchPart, and IfPart open scopes, o we have to close
+       // them.  WhilePart opens and closes its own scope.
+       CondStatement -> ForPart OptNL ThenPart OptNL WhilePart CondSuffix ${
+                       $0 = $<CS;
+                       $0->forpart = $<FP;
+                       $0->thenpart = $<TP;
+                       $0->looppart = $<WP;
+                       var_block_close(c, CloseSequential, $0);
                        }$
-               |  forPart WhilePart CondSuffix ${
-                       $0 = $<3;
-                       $0->forpart = $<1;
-                       $0->thenpart = NULL;
-                       $0->condpart = $2.condpart; $2.condpart = NULL;
-                       $0->dopart = $2.dopart; $2.dopart = NULL;
-                       var_block_close(c, CloseSequential);
+               | ForPart OptNL WhilePart CondSuffix ${
+                       $0 = $<CS;
+                       $0->forpart = $<FP;
+                       $0->looppart = $<WP;
+                       var_block_close(c, CloseSequential, $0);
                        }$
-               | whilePart CondSuffix ${
-                       $0 = $<2;
-                       $0->condpart = $1.condpart; $1.condpart = NULL;
-                       $0->dopart = $1.dopart; $1.dopart = NULL;
+               | WhilePart CondSuffix ${
+                       $0 = $<CS;
+                       $0->looppart = $<WP;
                        }$
-               | switchPart CondSuffix ${
-                       $0 = $<2;
-                       $0->condpart = $<1;
+               | SwitchPart OptNL CasePart CondSuffix ${
+                       $0 = $<CS;
+                       $0->condpart = $<SP;
+                       $CP->next = $0->casepart;
+                       $0->casepart = $<CP;
+                       var_block_close(c, CloseSequential, $0);
                        }$
-               | ifPart IfSuffix ${
-                       $0 = $<2;
-                       $0->condpart = $1.condpart; $1.condpart = NULL;
-                       $0->thenpart = $1.thenpart; $1.thenpart = NULL;
+               | SwitchPart : IN OptNL CasePart CondSuffix OUT Newlines ${
+                       $0 = $<CS;
+                       $0->condpart = $<SP;
+                       $CP->next = $0->casepart;
+                       $0->casepart = $<CP;
+                       var_block_close(c, CloseSequential, $0);
+                       }$
+               | IfPart IfSuffix ${
+                       $0 = $<IS;
+                       $0->condpart = $IP.condpart; $IP.condpart = NULL;
+                       $0->thenpart = $IP.thenpart; $IP.thenpart = NULL;
                        // This is where we close an "if" statement
-                       var_block_close(c, CloseSequential);
+                       var_block_close(c, CloseSequential, $0);
                        }$
 
        CondSuffix -> IfSuffix ${
                        $0 = $<1;
-                       // This is where we close scope of the whole
-                       // "for" or "while" statement
-                       var_block_close(c, CloseSequential);
+               }$
+               | Newlines CasePart CondSuffix ${
+                       $0 = $<CS;
+                       $CP->next = $0->casepart;
+                       $0->casepart = $<CP;
                }$
                | CasePart CondSuffix ${
-                       $0 = $<2;
-                       $1->next = $0->casepart;
-                       $0->casepart = $<1;
+                       $0 = $<CS;
+                       $CP->next = $0->casepart;
+                       $0->casepart = $<CP;
                }$
 
-       $void
-       Case -> case
-               | NEWLINE Case
-       $*casepart
-       CasePart -> Case Expression OpenScope Block ${
-                       $0 = calloc(1,sizeof(struct casepart));
-                       $0->value = $<2;
-                       $0->action = $<4;
-                       var_block_close(c, CloseParallel);
-               }$
+       IfSuffix -> Newlines ${ $0 = new(cond_statement); }$
+               | Newlines ElsePart ${ $0 = $<EP; }$
+               | ElsePart ${$0 = $<EP; }$
 
-       $*cond_statement
-       IfSuffix ->  ${ $0 = new(cond_statement); }$
-               | NEWLINE IfSuffix ${ $0 = $<2; }$
-               | else OpenScope Block ${
+       ElsePart -> else OpenBlock Newlines ${
                        $0 = new(cond_statement);
-                       $0->elsepart = $<3;
-                       var_block_close(c, CloseElse);
+                       $0->elsepart = $<OB;
+                       var_block_close(c, CloseElse, $0->elsepart);
                }$
                | else OpenScope CondStatement ${
                        $0 = new(cond_statement);
-                       $0->elsepart = $<3;
-                       var_block_close(c, CloseElse);
+                       $0->elsepart = $<CS;
+                       var_block_close(c, CloseElse, $0->elsepart);
                }$
 
-       $void
-       Then -> then
-               | NEWLINE Then
-       While -> while
-               | NEWLINE While
-       Do -> do
-               | NEWLINE Do
-       $*exec
-       // These scopes are closed in CondSuffix
-       forPart -> for OpenScope SimpleStatements ${
-                       $0 = reorder_bilist($<3);
-               }$
-               |  for OpenScope Block ${
-                       $0 = $<3;
+       $*casepart
+       CasePart -> case Expression OpenScope ColonBlock ${
+                       $0 = calloc(1,sizeof(struct casepart));
+                       $0->value = $<Ex;
+                       $0->action = $<Bl;
+                       var_block_close(c, CloseParallel, $0->action);
                }$
 
-       ThenPart -> Then OpenScope SimpleStatements ${
-                       $0 = reorder_bilist($<3);
-                       var_block_close(c, CloseSequential);
+       $*exec
+       // These scopes are closed in CondStatement
+       ForPart -> for OpenBlock ${
+                       $0 = $<Bl;
                }$
-               |  Then OpenScope Block ${
-                       $0 = $<3;
-                       var_block_close(c, CloseSequential);
+
+       ThenPart -> then OpenBlock ${
+                       $0 = $<OB;
+                       var_block_close(c, CloseSequential, $0);
                }$
 
-       // This scope is closed in CondSuffix
-       WhileHead -> While OpenScope Block ${
-               $0 = $<3;
+       $*binode
+       // This scope is closed in CondStatement
+       WhilePart -> while UseBlock OptNL do OpenBlock ${
+                       $0 = new(binode);
+                       $0->op = Loop;
+                       $0->left = $<UB;
+                       $0->right = $<OB;
+                       var_block_close(c, CloseSequential, $0->right);
+                       var_block_close(c, CloseSequential, $0);
                }$
-       whileHead -> while OpenScope Block ${
-               $0 = $<3;
+               | while OpenScope Expression OpenScope ColonBlock ${
+                       $0 = new(binode);
+                       $0->op = Loop;
+                       $0->left = $<Exp;
+                       $0->right = $<CB;
+                       var_block_close(c, CloseSequential, $0->right);
+                       var_block_close(c, CloseSequential, $0);
                }$
 
        $cond_statement
-       // This scope is closed in CondSuffix
-       whilePart -> while OpenScope Expression Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.dopart = $<4;
-               }$
-               | whileHead Do Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<1;
-                       $0.dopart = $<3;
-               }$
-       WhilePart -> While OpenScope Expression Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.dopart = $<4;
+       IfPart -> if UseBlock OptNL then OpenBlock ${
+                       $0.condpart = $<UB;
+                       $0.thenpart = $<OB;
+                       var_block_close(c, CloseParallel, $0.thenpart);
                }$
-               | WhileHead Do Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<1;
-                       $0.dopart = $<3;
+               | if OpenScope Expression OpenScope ColonBlock ${
+                       $0.condpart = $<Ex;
+                       $0.thenpart = $<CB;
+                       var_block_close(c, CloseParallel, $0.thenpart);
                }$
-
-       ifPart -> if OpenScope Expression OpenScope Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.thenpart = $<5;
-                       var_block_close(c, CloseParallel);
-               }$
-               | if OpenScope Block Then OpenScope Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.thenpart = $<6;
-                       var_block_close(c, CloseParallel);
+               | if OpenScope Expression OpenScope OptNL then Block ${
+                       $0.condpart = $<Ex;
+                       $0.thenpart = $<Bl;
+                       var_block_close(c, CloseParallel, $0.thenpart);
                }$
 
        $*exec
-       // This scope is closed in CondSuffix
-       switchPart -> switch OpenScope Expression ${
-                       $0 = $<3;
+       // This scope is closed in CondStatement
+       SwitchPart -> switch OpenScope Expression ${
+                       $0 = $<Ex;
                }$
-               | switch OpenScope Block ${
-                       $0 = $<3;
+               | switch UseBlock ${
+                       $0 = $<Bl;
                }$
 
+###### print binode cases
+       case Loop:
+               if (b->left && b->left->type == Xbinode &&
+                   cast(binode, b->left)->op == Block) {
+                       if (bracket)
+                               do_indent(indent, "while {\n");
+                       else
+                               do_indent(indent, "while\n");
+                       print_exec(b->left, indent+1, bracket);
+                       if (bracket)
+                               do_indent(indent, "} do {\n");
+                       else
+                               do_indent(indent, "do\n");
+                       print_exec(b->right, indent+1, bracket);
+                       if (bracket)
+                               do_indent(indent, "}\n");
+               } else {
+                       do_indent(indent, "while ");
+                       print_exec(b->left, 0, bracket);
+                       if (bracket)
+                               printf(" {\n");
+                       else
+                               printf(":\n");
+                       print_exec(b->right, indent+1, bracket);
+                       if (bracket)
+                               do_indent(indent, "}\n");
+               }
+               break;
+
 ###### print exec cases
 
        case Xcond_statement:
@@ -3720,44 +4464,19 @@ defined.
                struct casepart *cp;
                if (cs->forpart) {
                        do_indent(indent, "for");
-                       if (bracket) printf(" {\n"); else printf(":\n");
+                       if (bracket) printf(" {\n"); else printf("\n");
                        print_exec(cs->forpart, indent+1, bracket);
                        if (cs->thenpart) {
                                if (bracket)
                                        do_indent(indent, "} then {\n");
                                else
-                                       do_indent(indent, "then:\n");
+                                       do_indent(indent, "then\n");
                                print_exec(cs->thenpart, indent+1, bracket);
                        }
                        if (bracket) do_indent(indent, "}\n");
                }
-               if (cs->dopart) {
-                       // a loop
-                       if (cs->condpart && cs->condpart->type == Xbinode &&
-                           cast(binode, cs->condpart)->op == Block) {
-                               if (bracket)
-                                       do_indent(indent, "while {\n");
-                               else
-                                       do_indent(indent, "while:\n");
-                               print_exec(cs->condpart, indent+1, bracket);
-                               if (bracket)
-                                       do_indent(indent, "} do {\n");
-                               else
-                                       do_indent(indent, "do:\n");
-                               print_exec(cs->dopart, indent+1, bracket);
-                               if (bracket)
-                                       do_indent(indent, "}\n");
-                       } else {
-                               do_indent(indent, "while ");
-                               print_exec(cs->condpart, 0, bracket);
-                               if (bracket)
-                                       printf(" {\n");
-                               else
-                                       printf(":\n");
-                               print_exec(cs->dopart, indent+1, bracket);
-                               if (bracket)
-                                       do_indent(indent, "}\n");
-                       }
+               if (cs->looppart) {
+                       print_exec(cs->looppart, indent, bracket);
                } else {
                        // a condition
                        if (cs->casepart)
@@ -3769,12 +4488,12 @@ defined.
                                if (bracket)
                                        printf(" {\n");
                                else
-                                       printf(":\n");
+                                       printf("\n");
                                print_exec(cs->condpart, indent+1, bracket);
                                if (bracket)
                                        do_indent(indent, "}\n");
                                if (cs->thenpart) {
-                                       do_indent(indent, "then:\n");
+                                       do_indent(indent, "then\n");
                                        print_exec(cs->thenpart, indent+1, bracket);
                                }
                        } else {
@@ -3808,7 +4527,7 @@ defined.
                        if (bracket)
                                printf(" {\n");
                        else
-                               printf(":\n");
+                               printf("\n");
                        print_exec(cs->elsepart, indent+1, bracket);
                        if (bracket)
                                do_indent(indent, "}\n");
@@ -3816,11 +4535,18 @@ defined.
                break;
        }
 
+###### propagate binode cases
+       case Loop:
+               t = propagate_types(b->right, c, ok, Tnone, 0);
+               if (!type_compat(Tnone, t, 0))
+                       *ok = 0;        // UNTESTED
+               return propagate_types(b->left, c, ok, type, rules);
+
 ###### propagate exec cases
        case Xcond_statement:
        {
-               // forpart and dopart must return Tnone
-               // thenpart must return Tnone if there is a dopart,
+               // forpart and looppart->right must return Tnone
+               // thenpart must return Tnone if there is a loopart,
                // otherwise it is like elsepart.
                // condpart must:
                //    be bool if there is no casepart
@@ -3834,43 +4560,45 @@ defined.
 
                t = propagate_types(cs->forpart, c, ok, Tnone, 0);
                if (!type_compat(Tnone, t, 0))
-                       *ok = 0;
-               t = propagate_types(cs->dopart, c, ok, Tnone, 0);
-               if (!type_compat(Tnone, t, 0))
-                       *ok = 0;
-               if (cs->dopart) {
+                       *ok = 0;        // UNTESTED
+
+               if (cs->looppart) {
                        t = propagate_types(cs->thenpart, c, ok, Tnone, 0);
                        if (!type_compat(Tnone, t, 0))
-                               *ok = 0;
+                               *ok = 0;        // UNTESTED
                }
-               if (cs->casepart == NULL)
+               if (cs->casepart == NULL) {
                        propagate_types(cs->condpart, c, ok, Tbool, 0);
-               else {
+                       propagate_types(cs->looppart, c, ok, Tbool, 0);
+               } else {
                        /* Condpart must match case values, with bool permitted */
                        t = NULL;
                        for (cp = cs->casepart;
                             cp && !t; cp = cp->next)
                                t = propagate_types(cp->value, c, ok, NULL, 0);
                        if (!t && cs->condpart)
-                               t = propagate_types(cs->condpart, c, ok, NULL, Rboolok);
+                               t = propagate_types(cs->condpart, c, ok, NULL, Rboolok);        // UNTESTED
+                       if (!t && cs->looppart)
+                               t = propagate_types(cs->looppart, c, ok, NULL, Rboolok);        // UNTESTED
                        // Now we have a type (I hope) push it down
                        if (t) {
                                for (cp = cs->casepart; cp; cp = cp->next)
                                        propagate_types(cp->value, c, ok, t, 0);
                                propagate_types(cs->condpart, c, ok, t, Rboolok);
+                               propagate_types(cs->looppart, c, ok, t, Rboolok);
                        }
                }
                // (if)then, else, and case parts must return expected type.
-               if (!cs->dopart && !type)
+               if (!cs->looppart && !type)
                        type = propagate_types(cs->thenpart, c, ok, NULL, rules);
                if (!type)
                        type = propagate_types(cs->elsepart, c, ok, NULL, rules);
                for (cp = cs->casepart;
                     cp && !type;
-                    cp = cp->next)
-                       type = propagate_types(cp->action, c, ok, NULL, rules);
+                    cp = cp->next)     // UNTESTED
+                       type = propagate_types(cp->action, c, ok, NULL, rules); // UNTESTED
                if (type) {
-                       if (!cs->dopart)
+                       if (!cs->looppart)
                                propagate_types(cs->thenpart, c, ok, type, rules);
                        propagate_types(cs->elsepart, c, ok, type, rules);
                        for (cp = cs->casepart; cp ; cp = cp->next)
@@ -3880,50 +4608,55 @@ defined.
                        return NULL;
        }
 
+###### interp binode cases
+       case Loop:
+               // This just performs one iterration of the loop
+               rv = interp_exec(c, b->left, &rvtype);
+               if (rvtype == Tnone ||
+                   (rvtype == Tbool && rv.bool != 0))
+                       // rvtype is Tnone or Tbool, doesn't need to be freed
+                       interp_exec(c, b->right, NULL);
+               break;
+
 ###### interp exec cases
        case Xcond_statement:
        {
                struct value v, cnd;
+               struct type *vtype, *cndtype;
                struct casepart *cp;
-               struct cond_statement *c = cast(cond_statement, e);
+               struct cond_statement *cs = cast(cond_statement, e);
 
-               if (c->forpart)
-                       interp_exec(c->forpart);
-               do {
-                       if (c->condpart)
-                               cnd = interp_exec(c->condpart);
-                       else
-                               cnd.type = Tnone;
-                       if (!(cnd.type == Tnone ||
-                             (cnd.type == Tbool && cnd.bool != 0)))
-                               break;
-                       // cnd is Tnone or Tbool, doesn't need to be freed
-                       if (c->dopart)
-                               interp_exec(c->dopart);
-
-                       if (c->thenpart) {
-                               rv = interp_exec(c->thenpart);
-                               if (rv.type != Tnone || !c->dopart)
-                                       goto Xcond_done;
-                               free_value(rv);
+               if (cs->forpart)
+                       interp_exec(c, cs->forpart, NULL);
+               if (cs->looppart) {
+                       while ((cnd = interp_exec(c, cs->looppart, &cndtype)),
+                              cndtype == Tnone || (cndtype == Tbool && cnd.bool != 0))
+                               interp_exec(c, cs->thenpart, NULL);
+               } else {
+                       cnd = interp_exec(c, cs->condpart, &cndtype);
+                       if ((cndtype == Tnone ||
+                           (cndtype == Tbool && cnd.bool != 0))) {
+                               // cnd is Tnone or Tbool, doesn't need to be freed
+                               rv = interp_exec(c, cs->thenpart, &rvtype);
+                               // skip else (and cases)
+                               goto Xcond_done;
                        }
-               } while (c->dopart);
-
-               for (cp = c->casepart; cp; cp = cp->next) {
-                       v = interp_exec(cp->value);
-                       if (value_cmp(v, cnd) == 0) {
-                               free_value(v);
-                               free_value(cnd);
-                               rv = interp_exec(cp->action);
+               }
+               for (cp = cs->casepart; cp; cp = cp->next) {
+                       v = interp_exec(c, cp->value, &vtype);
+                       if (value_cmp(cndtype, vtype, &v, &cnd) == 0) {
+                               free_value(vtype, &v);
+                               free_value(cndtype, &cnd);
+                               rv = interp_exec(c, cp->action, &rvtype);
                                goto Xcond_done;
                        }
-                       free_value(v);
+                       free_value(vtype, &v);
                }
-               free_value(cnd);
-               if (c->elsepart)
-                       rv = interp_exec(c->elsepart);
+               free_value(cndtype, &cnd);
+               if (cs->elsepart)
+                       rv = interp_exec(c, cs->elsepart, &rvtype);
                else
-                       rv.type = Tnone;
+                       rvtype = Tnone;
        Xcond_done:
                break;
        }
@@ -3937,8 +4670,8 @@ 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
-program.  While the syntax will allow the main program to appear
+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.
 
 The various declarations do not return anything.  They store the
@@ -3947,22 +4680,30 @@ various declarations in the parse context.
 ###### Parser: grammar
 
        $void
-       Ocean -> DeclarationList
+       Ocean -> OptNL DeclarationList
+
+       ## declare terminals
+
+       OptNL ->
+               | OptNL NEWLINE
+       Newlines -> NEWLINE
+               | Newlines NEWLINE
 
        DeclarationList -> Declaration
                | DeclarationList Declaration
 
-       Declaration -> DeclareConstant
-               | DeclareProgram
-               | DeclareStruct
-               | NEWLINE
-               | ERROR NEWLINE ${
-                       tok_err(c,
+       Declaration -> ERROR Newlines ${
+                       tok_err(c,      // UNTESTED
                                "error: unhandled parse error", &$1);
                }$
+               | DeclareConstant
+               | DeclareFunction
+               | DeclareStruct
 
        ## top level grammar
 
+       ## Grammar
+
 ### The `const` section
 
 As well as being defined in with the code that uses them, constants
@@ -3988,19 +4729,20 @@ searching through for the Nth constant for decreasing N.
 
 ###### top level grammar
 
-       DeclareConstant -> const Open ConstList Close
-               | const Open SimpleConstList }
-               | const : ConstList
-               | const SimpleConstList NEWLINE
+       $TERM const
 
-       ConstList -> ComplexConsts
-               | NEWLINE ConstList
-       ComplexConsts -> ComplexConst ComplexConsts
-               | ComplexConst
-       ComplexConst -> SimpleConstList NEWLINE
+       DeclareConstant -> const { IN OptNL ConstList OUT OptNL } Newlines
+               | const { SimpleConstList } Newlines
+               | const IN OptNL ConstList OUT Newlines
+               | const SimpleConstList Newlines
+
+       ConstList -> ConstList SimpleConstLine
+               | SimpleConstLine
        SimpleConstList -> SimpleConstList ; Const
                | Const
                | SimpleConstList ;
+       SimpleConstLine -> SimpleConstList Newlines
+               | ERROR Newlines ${ tok_err(c, "Syntax error in constant", &$1); }$
 
        $*type
        CType -> Type   ${ $0 = $<1; }$
@@ -4017,11 +4759,12 @@ searching through for the Nth constant for decreasing N.
                        v->where_set = var;
                        var->var = v;
                        v->constant = 1;
+                       v->global = 1;
                } else {
-                       v = var_ref(c, $1.txt);
+                       struct variable *vorig = var_ref(c, $1.txt);
                        tok_err(c, "error: name already declared", &$1);
                        type_err(c, "info: this is where '%v' was first declared",
-                                v->where_decl, NULL, 0, NULL);
+                                vorig->where_decl, NULL, 0, NULL);
                }
                do {
                        ok = 1;
@@ -4030,10 +4773,10 @@ searching through for the Nth constant for decreasing N.
                if (!ok)
                        c->parse_error = 1;
                else if (v) {
-                       v->val = interp_exec($5);
+                       struct value res = interp_exec(c, $5, &v->type);
+                       global_alloc(c, v->type, v, &res);
                }
        } }$
-       | ERROR NEWLINE ${ tok_err(c, "Syntax error in constant", &$1); }$
 
 ###### print const decls
        {
@@ -4043,7 +4786,7 @@ searching through for the Nth constant for decreasing N.
                while (target != 0) {
                        int i = 0;
                        for (v = context.in_scope; v; v=v->in_scope)
-                               if (v->depth == 0) {
+                               if (v->depth == 0 && v->constant) {
                                        i += 1;
                                        if (i == target)
                                                break;
@@ -4051,16 +4794,17 @@ searching through for the Nth constant for decreasing N.
 
                        if (target == -1) {
                                if (i)
-                                       printf("const:\n");
+                                       printf("const\n");
                                target = i;
                        } else {
+                               struct value *val = var_value(&context, v);
                                printf("    %.*s :: ", v->name->name.len, v->name->name.txt);
-                               type_print(v->val.type, stdout);
+                               type_print(v->type, stdout);
                                printf(" = ");
-                               if (v->val.type == Tstr)
+                               if (v->type == Tstr)
                                        printf("\"");
-                               print_value(v->val);
-                               if (v->val.type == Tstr)
+                               print_value(v->type, val);
+                               if (v->type == Tstr)
                                        printf("\"");
                                printf("\n");
                                target -= 1;
@@ -4068,145 +4812,226 @@ searching through for the Nth constant for decreasing N.
                }
        }
 
-### Finally the whole program.
+### Function declarations
 
-Somewhat reminiscent of Pascal a (current) Ocean program starts with
-the keyword "program" and a list of variable names which are assigned
-values from command line arguments.  Following this is a `block` which
-is the code to execute.  Unlike Pascal, constants and other
-declarations come *before* the program.
+The code in an Ocean program is all stored in function declarations.
+One of the functions must be named `main` and it must accept an array of
+strings as a parameter - the command line arguments.
 
-As this is the top level, several things are handled a bit
-differently.
-The whole program is not interpreted by `interp_exec` as that isn't
-passed the argument list which the program requires.  Similarly type
-analysis is a bit more interesting at this level.
+As this is the top level, several things are handled a bit differently.
+The function is not interpreted by `interp_exec` as that isn't passed
+the argument list which the program requires.  Similarly type analysis
+is a bit more interesting at this level.
 
-###### Binode types
-       Program,
+###### ast functions
 
-###### top level grammar
+       static struct variable *declare_function(struct parse_context *c,
+                                               struct variable *name,
+                                               struct binode *args,
+                                               struct type *ret,
+                                               struct exec *code)
+       {
+               struct text funcname = {" func", 5};
+               if (name) {
+                       struct value fn = {.function = code};
+                       name->type = add_type(c, funcname, &function_prototype);
+                       name->type->function.params = reorder_bilist(args);
+                       name->type->function.return_type = ret;
+                       global_alloc(c, name->type, name, &fn);
+                       var_block_close(c, CloseFunction, code);
+                       name->type->function.scope = c->out_scope;
+               } else {
+                       free_binode(args);
+                       free_type(ret);
+                       free_exec(code);
+                       var_block_close(c, CloseFunction, NULL);
+               }
+               c->out_scope = NULL;
+               return name;
+       }
 
-       DeclareProgram -> Program ${ {
-               if (c->prog)
-                       type_err(c, "Program defined a second time",
-                                $1, NULL, 0, NULL);
-               else
-                       c->prog = $<1;
-       } }$
+###### declare terminals
+       $TERM return
 
-       $*binode
-       Program -> program OpenScope Varlist Block ${
-               $0 = new(binode);
-               $0->op = Program;
-               $0->left = reorder_bilist($<3);
-               $0->right = $<4;
-               var_block_close(c, CloseSequential);
-               if (c->scope_stack && !c->parse_error) abort();
-               }$
+###### top level grammar
 
-       Varlist -> Varlist ArgDecl ${
-                       $0 = new(binode);
-                       $0->op = Program;
-                       $0->left = $<1;
-                       $0->right = $<2;
+       $*variable
+       DeclareFunction -> func FuncName ( OpenScope ArgsLine ) Block Newlines ${
+                       $0 = declare_function(c, $<FN, $<Ar, Tnone, $<Bl);
+               }$
+               | func FuncName IN OpenScope Args OUT OptNL do Block Newlines ${
+                       $0 = declare_function(c, $<FN, $<Ar, Tnone, $<Bl);
+               }$
+               | func FuncName NEWLINE OpenScope OptNL do Block Newlines ${
+                       $0 = declare_function(c, $<FN, NULL, Tnone, $<Bl);
+               }$
+               | func FuncName ( OpenScope ArgsLine ) : Type Block Newlines ${
+                       $0 = declare_function(c, $<FN, $<Ar, $<Ty, $<Bl);
+               }$
+               | func FuncName IN OpenScope Args OUT OptNL return Type Newlines do Block Newlines ${
+                       $0 = declare_function(c, $<FN, $<Ar, $<Ty, $<Bl);
+               }$
+               | func FuncName NEWLINE OpenScope return Type Newlines do Block Newlines ${
+                       $0 = declare_function(c, $<FN, NULL, $<Ty, $<Bl);
                }$
-               | ${ $0 = NULL; }$
 
-       $*var
-       ArgDecl -> IDENTIFIER ${ {
-               struct variable *v = var_decl(c, $1.txt);
-               $0 = new(var);
-               $0->var = v;
-       } }$
+###### print func decls
+       {
+               struct variable *v;
+               int target = -1;
 
-       ## Grammar
+               while (target != 0) {
+                       int i = 0;
+                       for (v = context.in_scope; v; v=v->in_scope)
+                               if (v->depth == 0 && v->type && v->type->check_args) {
+                                       i += 1;
+                                       if (i == target)
+                                               break;
+                               }
 
-###### print binode cases
-       case Program:
-               do_indent(indent, "program");
-               for (b2 = cast(binode, b->left); b2; b2 = cast(binode, b2->right)) {
-                       printf(" ");
-                       print_exec(b2->left, 0, 0);
+                       if (target == -1) {
+                               target = i;
+                       } else {
+                               struct value *val = var_value(&context, v);
+                               printf("func %.*s", v->name->name.len, v->name->name.txt);
+                               v->type->print_type_decl(v->type, stdout);
+                               if (brackets)
+                                       print_exec(val->function, 0, brackets);
+                               else
+                                       print_value(v->type, val);
+                               printf("/* frame size %d */\n", v->type->function.local_size);
+                               target -= 1;
+                       }
                }
-               if (bracket)
-                       printf(" {\n");
-               else
-                       printf(":\n");
-               print_exec(b->right, indent+1, bracket);
-               if (bracket)
-                       do_indent(indent, "}\n");
-               break;
-
-###### propagate binode cases
-       case Program: abort();          // NOTEST
+       }
 
 ###### core functions
 
-       static int analyse_prog(struct exec *prog, struct parse_context *c)
+       static int analyse_funcs(struct parse_context *c)
        {
-               struct binode *b = cast(binode, prog);
+               struct variable *v;
+               int all_ok = 1;
+               for (v = c->in_scope; v; v = v->in_scope) {
+                       struct value *val;
+                       int ok = 1;
+                       if (v->depth != 0 || !v->type || !v->type->check_args)
+                               continue;
+                       val = var_value(c, v);
+                       do {
+                               ok = 1;
+                               propagate_types(val->function, c, &ok,
+                                               v->type->function.return_type, 0);
+                       } while (ok == 2);
+                       if (ok)
+                               /* Make sure everything is still consistent */
+                               propagate_types(val->function, c, &ok,
+                                               v->type->function.return_type, 0);
+                       if (!ok)
+                               all_ok = 0;
+                       if (!v->type->function.return_type->dup) {
+                               type_err(c, "error: function cannot return value of type %1", 
+                                        v->where_decl, v->type->function.return_type, 0, NULL);
+                       }
+
+                       scope_finalize(c, v->type);
+               }
+               return all_ok;
+       }
+
+       static int analyse_main(struct type *type, struct parse_context *c)
+       {
+               struct binode *bp = type->function.params;
+               struct binode *b;
                int ok = 1;
+               int arg = 0;
+               struct type *argv_type;
+               struct text argv_type_name = { " argv", 5 };
 
-               if (!b)
-                       return 0;       // NOTEST
-               do {
-                       ok = 1;
-                       propagate_types(b->right, c, &ok, Tnone, 0);
-               } while (ok == 2);
-               if (!ok)
-                       return 0;
+               argv_type = add_type(c, argv_type_name, &array_prototype);
+               argv_type->array.member = Tstr;
+               argv_type->array.unspec = 1;
 
-               for (b = cast(binode, b->left); b; b = cast(binode, b->right)) {
-                       struct var *v = cast(var, b->left);
-                       if (!v->var->val.type) {
-                               v->var->where_set = b;
-                               v->var->val = val_prepare(Tstr);
+               for (b = bp; b; b = cast(binode, b->right)) {
+                       ok = 1;
+                       switch (arg++) {
+                       case 0: /* argv */
+                               propagate_types(b->left, c, &ok, argv_type, 0);
+                               break;
+                       default: /* invalid */  // NOTEST
+                               propagate_types(b->left, c, &ok, Tnone, 0);     // NOTEST
                        }
+                       if (!ok)
+                               c->parse_error = 1;
                }
-               b = cast(binode, prog);
-               do {
-                       ok = 1;
-                       propagate_types(b->right, c, &ok, Tnone, 0);
-               } while (ok == 2);
-               if (!ok)
-                       return 0;
 
-               /* Make sure everything is still consistent */
-               propagate_types(b->right, c, &ok, Tnone, 0);
-               return !!ok;
+               return !c->parse_error;
        }
 
-       static void interp_prog(struct exec *prog, char **argv)
+       static void interp_main(struct parse_context *c, int argc, char **argv)
        {
-               struct binode *p = cast(binode, prog);
+               struct value *progp = NULL;
+               struct text main_name = { "main", 4 };
+               struct variable *mainv;
                struct binode *al;
+               int anum = 0;
                struct value v;
+               struct type *vtype;
 
-               if (!prog)
-                       return;         // NOTEST
-               al = cast(binode, p->left);
+               mainv = var_ref(c, main_name);
+               if (mainv)
+                       progp = var_value(c, mainv);
+               if (!progp || !progp->function) {
+                       fprintf(stderr, "oceani: no main function found.\n");
+                       c->parse_error = 1;
+                       return;
+               }
+               if (!analyse_main(mainv->type, c)) {
+                       fprintf(stderr, "oceani: main has wrong type.\n");
+                       c->parse_error = 1;
+                       return;
+               }
+               al = mainv->type->function.params;
+
+               c->local_size = mainv->type->function.local_size;
+               c->local = calloc(1, c->local_size);
                while (al) {
                        struct var *v = cast(var, al->left);
-                       struct value *vl = &v->var->val;
-
-                       if (argv[0] == NULL) {
-                               printf("Not enough args\n");
-                               exit(1);
+                       struct value *vl = var_value(c, v->var);
+                       struct value arg;
+                       struct type *t;
+                       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->prepare_type(c, t, 0);
+                               array_init(v->var->type, vl);
+                               for (i = 0; i < argc; i++) {
+                                       struct value *vl2 = vl->array + i * v->var->type->array.member->size;
+
+                                       arg.str.txt = argv[i];
+                                       arg.str.len = strlen(argv[i]);
+                                       free_value(Tstr, vl2);
+                                       dup_value(Tstr, &arg, vl2);
+                               }
+                               break;
                        }
                        al = cast(binode, al->right);
-                       free_value(*vl);
-                       *vl = parse_value(vl->type, argv[0]);
-                       if (vl->type == NULL)
-                               exit(1);
-                       argv++;
                }
-               v = interp_exec(p->right);
-               free_value(v);
+               v = interp_exec(c, progp->function, &vtype);
+               free_value(vtype, &v);
+               free(c->local);
+               c->local = NULL;
        }
 
-###### interp binode cases
-       case Program: abort();  // NOTEST
+###### ast functions
+       void free_variable(struct variable *v)
+       {
+       }
 
 ## And now to test it out.
 
@@ -4224,29 +5049,31 @@ things which will likely grow as the languages grows.
 
 ###### demo: hello
 
-       const:
+       const
                pi ::= 3.141_592_6
                four ::= 2 + 2 ; five ::= 10/2
        const pie ::= "I like Pie";
                cake ::= "The cake is"
                  ++ " a lie"
 
-       struct fred:
+       struct fred
                size:[four]number
                name:string
                alive:Boolean
 
-       program A B:
+       func main(argv:[argc::]string)
                print "Hello World, what lovely oceans you have!"
                print "Are there", five, "?"
                print pi, pie, "but", cake
 
+               A := $argv[1]; B := $argv[2]
+
                /* When a variable is defined in both branches of an 'if',
                 * and used afterwards, the variables are merged.
                 */
                if A > B:
                        bigger := "yes"
-               else:
+               else
                        bigger := "no"
                print "Is", A, "bigger than", B,"? ", bigger
                /* If a variable is not used after the 'if', no
@@ -4255,7 +5082,7 @@ things which will likely grow as the languages grows.
                if A > B * 2:
                        double:string = "yes"
                        print A, "is more than twice", B, "?", double
-               else:
+               else
                        double := B*2
                        print "double", B, "is", double
 
@@ -4266,15 +5093,15 @@ things which will likely grow as the languages grows.
                        while a != b:
                                if a < b:
                                        b = b - a
-                               else:
+                               else
                                        a = a - b
                        print "GCD of", A, "and", B,"is", a
                else if a <= 0:
                        print a, "is not positive, cannot calculate GCD"
-               else:
+               else
                        print b, "is not positive, cannot calculate GCD"
 
-               for:
+               for
                        togo := 10
                        f1 := 1; f2 := 1
                        print "Fibonacci:", f1,f2,
@@ -4287,25 +5114,26 @@ things which will likely grow as the languages grows.
                print ""
 
                /* Binary search... */
-               for:
+               for
                        lo:= 0; hi := 100
                        target := 77
-               while:
+               while
                        mid := (lo + hi) / 2
                        if mid == target:
                                use Found
                        if mid < target:
                                lo = mid
-                       else:
+                       else
                                hi = mid
                        if hi - lo < 1:
+                               lo = mid
                                use GiveUp
                        use True
-               do: pass
+               do pass
                case Found:
                        print "Yay, I found", target
                case GiveUp:
-                       print "Closest I found was", mid
+                       print "Closest I found was", lo
 
                size::= 10
                list:[size]number
@@ -4332,6 +5160,8 @@ things which will likely grow as the languages grows.
                        print "", list[i],
                print
 
+               if 1 == 2 then print "yes"; else print "no"
+
                bob:fred
                bob.name = "Hello"
                bob.alive = (bob.name == "Hello")