]> ocean-lang.org Git - ocean/blobdiff - csrc/oceani.mdc
oceani: drop "program" in favour of "func"
[ocean] / csrc / oceani.mdc
index 062eaed69f82f7c4762b053977069609a838e4c2..fd753ce5e0b0d43484fb6959e19cb30c7ee04aab 100644 (file)
@@ -1,8 +1,8 @@
-# Ocean Interpreter - Stoney Creek version
+# Ocean Interpreter - Jamison Creek version
 
-Ocean is intended to be an compiled language, so this interpreter is
+Ocean is intended to be a compiled language, so this interpreter is
 not targeted at being the final product.  It is, rather, an intermediate
-stage, and fills that role in two distinct ways.
+stage and fills that role in two distinct ways.
 
 Firstly, it exists as a platform to experiment with the early language
 design.  An interpreter is easy to write and easy to get working, so
@@ -29,35 +29,33 @@ be.
 
 ## Current version
 
-This second version of the interpreter exists to test out the
-structured statement providing conditions and iteration, and simple
-variable scoping.  Clearly we need some minimal other functionality so
-that values can be tested and instructions iterated over.  All that
-functionality is clearly not normative at this stage (not that
-anything is **really** normative yet) and will change, so early test
-code will certainly break in later versions.
+This third version of the interpreter exists to test out some initial
+ideas relating to types.  Particularly it adds arrays (indexed from
+zero) and simple structures.  Basic control flow and variable scoping
+are already fairly well established, as are basic numerical and
+boolean operators.
 
-The under-test parts of the language are:
+Some operators that have only recently been added, and so have not
+generated all that much experience yet are "and then" and "or else" as
+short-circuit Boolean operators, and the "if ... else" trinary
+operator which can select between two expressions based on a third
+(which appears syntactically in the middle).
 
- - conditional/looping structured statements
- - the `use` statement which is needed for that
- - Variable binding using ":=" and "::=", and assignment using "=".
+The "func" clause currently only allows a "main" function to be
+declared.  That will be extended when proper function support is added.
 
-Elements which are present to make a usable language are:
+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.
 
- - "blocks" of multiple statements.
- - `pass`: a statement which does nothing.
- - expressions: `+`, `-`, `*`, `/` can apply to numbers and `++` can
-   catenate strings.  `and`, `or`, `not` manipulate Booleans, and
-   normal comparison operators can work on all three types.
- - `print`: will print the values in a list of expressions.
- - `program`: is given a list of identifiers to initialize from
-   arguments.
+The current scalar types are "number", "Boolean", and "string".
+Boolean will likely stay in its current form, the other two might, but
+could just as easily be changed.
 
 ## Naming
 
 Versions of the interpreter which obviously do not support a complete
-language will be named after creeks and streams.  This one is Stoney
+language will be named after creeks and streams.  This one is Jamison
 Creek.
 
 Once we have something reasonably resembling a complete language, the
@@ -73,10 +71,11 @@ out the program from the parsed internal structure.  This is useful
 for validating the parsing.
 So the main requirements of the interpreter are:
 
-- Parse the program, possibly with tracing
-- Analyse the parsed program to ensure consistency
-- print the program
-- execute the program
+- Parse the program, possibly with tracing,
+- Analyse the parsed program to ensure consistency,
+- Print the program,
+- Execute the "main" function in the program, if no parsing or
+  consistency errors were found.
 
 This is all performed by a single C program extracted with
 `parsergen`.
@@ -85,9 +84,12 @@ 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
+structures can be used.
+
 ###### File: oceani.mk
 
        myCFLAGS := -Wall -g -fplan9-extensions
@@ -106,11 +108,13 @@ option.
 
 ###### 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
        };
 
@@ -123,6 +127,9 @@ option.
        #define config2context(_conf) container_of(_conf, struct parse_context, \
                config)
 
+###### Parser: reduce
+       struct parse_context *c = config2context(config);
+
 ###### Parser: code
 
        #include <unistd.h>
@@ -148,8 +155,8 @@ option.
        ## core functions
 
        #include <getopt.h>
-       static char Usage[] = "Usage: oceani --trace --print --noexec --brackets"
-                             "--section=SectionName prog.ocn\n";
+       static char Usage[] =
+               "Usage: oceani --trace --print --noexec --brackets --section=SectionName prog.ocn\n";
        static const struct option long_options[] = {
                {"trace",     0, NULL, 't'},
                {"print",     0, NULL, 'p'},
@@ -164,19 +171,17 @@ option.
                int fd;
                int len;
                char *file;
-               struct section *s;
+               struct section *s, *ss;
                char *section = NULL;
                struct parse_context context = {
                        .config = {
-                               .ignored = (1 << TK_line_comment)
-                                        | (1 << TK_block_comment),
-                               .number_chars = ".,_+-",
+                               .ignored = (1 << TK_mark),
+                               .number_chars = ".,_+- ",
                                .word_start = "_",
                                .word_cont = "_",
                        },
                };
                int doprint=0, dotrace=0, doexec=1, brackets=0;
-               struct exec **prog;
                int opt;
                while ((opt = getopt_long(argc, argv, options, long_options, NULL))
                       != -1) {
@@ -208,67 +213,69 @@ option.
                                argv[optind]);
                        exit(1);
                }
+
+               ## context initialization
+
                if (section) {
-                       struct section *ss;
                        for (ss = s; ss; ss = ss->next) {
                                struct text sec = ss->section;
                                if (sec.len == strlen(section) &&
                                    strncmp(sec.txt, section, sec.len) == 0)
                                        break;
                        }
-                       if (ss)
-                               prog = parse_oceani(ss->code, &context.config,
-                                                   dotrace ? stderr : NULL);
-                       else {
+                       if (!ss) {
                                fprintf(stderr, "oceani: cannot find section %s\n",
                                        section);
                                exit(1);
                        }
                } else
-                       prog = parse_oceani(s->code, &context.config,
-                                   dotrace ? stderr : NULL);
-               if (!prog) {
-                       fprintf(stderr, "oceani: fatal parser error.\n");
+                       ss = s;                         // NOTEST
+               parse_oceani(ss->code, &context.config, dotrace ? stderr : NULL);
+
+               if (!context.prog) {
+                       fprintf(stderr, "oceani: no main function found.\n");
                        context.parse_error = 1;
                }
-               if (prog && doprint)
-                       print_exec(*prog, 0, brackets);
-               if (prog && doexec && !context.parse_error) {
-                       if (!analyse_prog(*prog, &context)) {
+               if (context.prog && doprint) {
+                       ## print const decls
+                       ## print type decls
+                       print_exec(context.prog, 0, brackets);
+               }
+               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(*prog, argv+optind+1);
-               }
-               if (prog) {
-                       free_exec(*prog);
-                       free(prog);
+                       interp_prog(&context, context.prog, argc - optind, argv+optind);
                }
+               free_exec(context.prog);
+
                while (s) {
                        struct section *t = s->next;
                        code_free(s->code);
                        free(s);
                        s = t;
                }
-               ## free context
+               ## free context vars
+               ## free context types
                exit(context.parse_error ? 1 : 0);
        }
 
 ### Analysis
 
-These four requirements of parse, analyse, print, interpret apply to
+The four requirements of parse, analyse, print, interpret apply to
 each language element individually so that is how most of the code
 will be structured.
 
 Three of the four are fairly self explanatory.  The one that requires
 a little explanation is the analysis step.
 
-The current language design does not require (or even allow) the types
-of variables to be declared, but they must still have a single type.
-Different operations impose different requirements on the variables,
-for example addition requires both arguments to be numeric, and
-assignment requires the variable on the left to have the same type as
-the expression on the right.
+The current language design does not require the types of variables to
+be declared, but they must still have a single type.  Different
+operations impose different requirements on the variables, for example
+addition requires both arguments to be numeric, and assignment
+requires the variable on the left to have the same type as the
+expression on the right.
 
 Analysis involves propagating these type requirements around and
 consequently setting the type of each variable.  If any requirements
@@ -278,29 +285,21 @@ 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 type
-of variable, but we must first know the correct type so any required
-conversion can happen.  If a variable is associated with a command
-line argument but no type can be interpreted (e.g. the variable is
-only ever used in a `print` statement), then the type is set to
-'string'.
-
 Undeclared names may only appear in "use" statements and "case" expressions.
 These names are given a type of "label" and a unique value.
 This allows them to fill the role of a name in an enumerated type, which
 is useful for testing the `switch` statement.
 
 As we will see, the condition part of a `while` statement can return
-either a Boolean or some other type.  This requires that the expect
-type that gets passed around comprises a type (`enum vtype`) and a
-flag to indicate that `Vbool` is also permitted.
+either a Boolean or some other type.  This requires that the expected
+type that gets passed around comprises a type and a flag to indicate
+that `Tbool` is also permitted.
 
 As there are, as yet, no distinct types that are compatible, there
 isn't much subtlety in the analysis.  When we have distinct number
@@ -310,8 +309,9 @@ types, this will become more interesting.
 
 When analysis discovers an inconsistency it needs to report an error;
 just refusing to run the code ensures that the error doesn't cascade,
-but by itself it isn't very useful.  A clear understand of the sort of
-error message that are useful will help guide the process of analysis.
+but by itself it isn't very useful.  A clear understanding of the sort
+of error message that are useful will help guide the process of
+analysis.
 
 At a simplistic level, the only sort of error that type analysis can
 report is that the type of some construct doesn't match a contextual
@@ -336,13 +336,19 @@ 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.
 
+As well as type errors, we sometimes need to report problems with
+tokens, which might be unexpected or might name a type that has not
+been defined.  For these we have `tok_err()` which reports an error
+with a given token.  Each of the error functions sets the flag in the
+context so indicate that parsing failed.
+
 ###### forward decls
 
        static void fput_loc(struct exec *loc, FILE *f);
@@ -362,13 +368,13 @@ in too.
                        }
                        fmt++;
                        switch (*fmt) {
-                       case '%': fputc(*fmt, stderr); break;
-                       default: fputc('?', stderr); break;
+                       case '%': fputc(*fmt, stderr); break;   // NOTEST
+                       default: fputc('?', stderr); break;     // NOTEST
                        case '1':
-                               fputs(t1 ? t1->name : "*unknown*", stderr);
+                               type_print(t1, stderr);
                                break;
                        case '2':
-                               fputs(t2 ? t2->name : "*unknown*", stderr);
+                               type_print(t2, stderr);
                                break;
                        ## format cases
                        }
@@ -379,62 +385,212 @@ in too.
 
        static void tok_err(struct parse_context *c, char *fmt, struct token *t)
        {
-               fprintf(stderr, "%s:%d:%d: %s\n", c->file_name, t->line, t->col, fmt);
+               fprintf(stderr, "%s:%d:%d: %s: %.*s\n", c->file_name, t->line, t->col, fmt,
+                       t->txt.len, t->txt.txt);
                c->parse_error = 1;
        }
 
-## Data Structures
-
-One last introductory step before detailing the language elements and
-providing their four requirements is to establish the data structures
-to store these elements.
+## Entities: declared and predeclared.
 
-There are two key objects that we need to work with: executable
-elements which comprise the program, and values which the program
-works with.  Between these are the variables in their various scopes
-which hold the values, and types which classify the values stored and
-manipulatd by executables.
+There are various "things" that the language and/or the interpreter
+needs to know about to parse and execute a program.  These include
+types, variables, values, and executable code.  These are all lumped
+together under the term "entities" (calling them "objects" would be
+confusing) and introduced here.  The following section will present the
+different specific code elements which comprise or manipulate these
+various entities.
 
 ### 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
-existance of these conversion functions enable types to determine if
-they are compatible with other types.
+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.
 
 ###### ast
 
+       struct value {
+               union {
+                       char ptr[1];
+                       ## value union fields
+               };
+       };
+
        struct type {
-               char *name;
-               struct value (*init)(struct type *type);
-               struct value (*parse)(struct type *type, char *str);
-               void (*print)(struct value val);
-               int (*cmp_order)(struct value v1, struct value v2);
-               int (*cmp_eq)(struct value v1, struct value v2);
-               struct value (*dup)(struct value val);
-               void (*free)(struct value val);
-               struct type *(*compat)(struct type *this, struct type *other);
+               struct text name;
+               struct type *next;
+               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 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);
                long long (*to_int)(struct value *v);
                double (*to_float)(struct value *v);
                int (*to_mpq)(mpq_t *q, struct value *v);
-               ## type fields
+               ## type functions
+               union {
+                       ## type union fields
+               };
        };
 
-### Values
+###### parse context
+
+       struct type *typelist;
+
+###### ast functions
+
+       static struct type *find_type(struct parse_context *c, struct text s)
+       {
+               struct type *l = c->typelist;
+
+               while (l &&
+                      text_cmp(l->name, s) != 0)
+                               l = l->next;
+               return l;
+       }
+
+       static struct type *add_type(struct parse_context *c, struct text s,
+                                    struct type *proto)
+       {
+               struct type *n;
+
+               n = calloc(1, sizeof(*n));
+               *n = *proto;
+               n->name = s;
+               n->next = c->typelist;
+               c->typelist = n;
+               return n;
+       }
+
+       static void free_type(struct type *t)
+       {
+               /* The type is always a reference to something in the
+                * context, so we don't need to free anything.
+                */
+       }
+
+       static void free_value(struct type *type, struct value *v)
+       {
+               if (type && v)
+                       type->free(type, v);
+       }
+
+       static void type_print(struct type *type, FILE *f)
+       {
+               if (!type)
+                       fputs("*unknown*type*", f);     // NOTEST
+               else if (type->name.len)
+                       fprintf(f, "%.*s", type->name.len, type->name.txt);
+               else if (type->print_type)
+                       type->print_type(type, f);
+               else
+                       fputs("*invalid*type*", f);     // NOTEST
+       }
+
+       static void val_init(struct type *type, struct value *val)
+       {
+               if (type && type->init)
+                       type->init(type, val);
+       }
+
+       static void dup_value(struct type *type,
+                             struct value *vold, struct value *vnew)
+       {
+               if (type && type->dup)
+                       type->dup(type, vold, vnew);
+       }
+
+       static int value_cmp(struct type *tl, struct type *tr,
+                            struct value *left, struct value *right)
+       {
+               if (tl && tl->cmp_order)
+                       return tl->cmp_order(tl, tr, left, right);
+               if (tl && tl->cmp_eq)
+                       return tl->cmp_eq(tl, tr, left, right);
+               return -1;                              // NOTEST
+       }
+
+       static void print_value(struct type *type, struct value *v)
+       {
+               if (type && type->print)
+                       type->print(type, v);
+               else
+                       printf("*Unknown*");            // NOTEST
+       }
+
+###### forward decls
 
-Values can be numbers, which we represent as multi-precision
-fractions, strings, Booleans and labels.  When analysing the program
-we also need to allow for places where no value is meaningful (type
-`Tnone`) and where we don't know what type to expect yet (type is
-`NULL`).
+       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 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
+
+       while (context.typelist) {
+               struct type *t = context.typelist;
+
+               context.typelist = t->next;
+               if (t->free_type)
+                       t->free_type(t);
+               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
+multi-precision fractions, strings, Booleans and labels.  When
+analysing the program we also need to allow for places where no value
+is meaningful (type `Tnone`) and where we don't know what type to
+expect yet (type is `NULL`).
 
 Values are never shared, they are always copied when used, and freed
 when no longer needed.
@@ -445,190 +601,139 @@ with anything.  There are two special cases with type compatibility,
 both related to the Conditional Statement which will be described
 later.  In some cases a Boolean can be accepted as well as some other
 primary type, and in others any type is acceptable except a label (`Vlabel`).
-A separate function encode these cases will simplify some code later.
+A separate function encoding these cases will simplify some code later.
+
+###### 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;
+               if ((rules & Rnolabel) && have == Tlabel)
+                       return 0;
+               if (!require || !have)
+                       return 1;
+
+               if (require->compat)
+                       return require->compat(require, have);
 
-When assigning command line arguments to variables, we need to be able
-to parse each type from a string.
+               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
        LDLIBS := $(filter-out $(myLDLIBS),$(LDLIBS)) $(myLDLIBS)
 
-###### type fields
+###### type union fields
        enum vtype {Vnone, Vstr, Vnum, Vbool, Vlabel} vtype;
 
-###### ast
-       struct value {
-               struct type *type;
-               union {
-                       struct text str;
-                       mpq_t num;
-                       int bool;
-                       void *label;
-               };
-       };
-
-       enum val_rules {Rnolabel = 1<<0, Rboolok = 1<<1};
-
-###### format cases
-       case 'r':
-               if (rules & Rnolabel)
-                       fputs(" (labels not permitted)", stderr);
-               break;
+###### value union fields
+       struct text str;
+       mpq_t num;
+       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;
                }
        }
 
-       static void free_value(struct value v)
-       {
-               if (v.type)
-                       v.type->free(v);
-       }
-
-       static int vtype_compat(struct type *require, struct type *have, int rules)
-       {
-               if ((rules & Rboolok) && have == &Tbool)
-                       return 1;
-               if ((rules & Rnolabel) && have == &Tlabel)
-                       return 0;
-               if (!require || !have)
-                       return 1;
-
-               return require == have;
-       }
-
 ###### value functions
 
-       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:abort();
+               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:
-                       rv.label = NULL;
+                       val->label = NULL;
                        break;
                }
-               return rv;
-       }
-
-       static struct value val_init(struct type *type)
-       {
-               struct value rv;
-
-               if (type)
-                       return type->init(type);
-               rv.type = type;
-               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) {
-               case Vnone:
-                       break;
+               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 struct value dup_value(struct value v)
-       {
-               if (v.type)
-                       return v.type->dup(v);
-               return v;
        }
 
-       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;
-               switch (left.type->vtype) {
-               case Vlabel: cmp = left.label == right.label ? 0 : 1; break;
-               case Vnum: cmp = mpq_cmp(left.num, right.num); break;
-               case Vstr: cmp = text_cmp(left.str, right.str); break;
-               case Vbool: cmp = left.bool - right.bool; break;
-               case Vnone: cmp = 0;
+               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 int value_cmp(struct value left, struct value right)
+       static void _print_value(struct type *type, struct value *v)
        {
-               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;
-       }
-
-       static struct text text_join(struct text a, struct text b)
-       {
-               struct text rv;
-               rv.len = a.len + b.len;
-               rv.txt = malloc(rv.len);
-               memcpy(rv.txt, a.txt, a.len);
-               memcpy(rv.txt+a.len, b.txt, b.len);
-               return rv;
-       }
-
-       static void _print_value(struct value v)
-       {
-               switch (v.type->vtype) {
-               case Vnone:
-                       printf("*no-value*"); break;
-               case Vlabel:
-                       printf("*label-%p*", v.label); break;
+               switch (type->vtype) {
+               case Vnone:                             // NOTEST
+                       printf("*no-value*"); break;    // NOTEST
+               case Vlabel:                            // 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;
@@ -636,118 +741,47 @@ to parse each type from a string.
                }
        }
 
-       static void print_value(struct value v)
-       {
-               if (v.type && v.type->print)
-                       v.type->print(v);
-               else
-                       printf("*Unknown*");
-       }
+       static void _free_value(struct type *type, struct value *v);
 
-       static struct value _parse_value(struct type *type, char *arg)
-       {
-               struct value val;
-               struct text tx;
-               int neg = 0;
-               char tail[3] = "";
+       static struct type base_prototype = {
+               .init = _val_init,
+               .print = _print_value,
+               .cmp_order = _value_cmp,
+               .cmp_eq = _value_cmp,
+               .dup = _dup_value,
+               .free = _free_value,
+       };
 
-               val.type = type;
-               switch(type->vtype) {
-               case Vlabel:
-               case Vnone:
-                       val.type = NULL;
-                       break;
-               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 struct type *Tbool, *Tstr, *Tnum, *Tnone, *Tlabel;
 
-       static struct value parse_value(struct type *type, char *arg)
+###### ast functions
+       static struct type *add_base_type(struct parse_context *c, char *n,
+                                         enum vtype vt, int size)
        {
-               struct value rv;
+               struct text txt = { n, strlen(n) };
+               struct type *t;
 
-               if (type && type->parse)
-                       return type->parse(type, arg);
-               rv.type = NULL;
-               return rv;
+               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;
+               return t;
        }
 
-       static void _free_value(struct value v);
-
-       #define BaseType \
-               .init = _val_init,              \
-               .parse = _parse_value,          \
-               .print = _print_value,          \
-               .cmp_order = _value_cmp,        \
-               .cmp_eq = _value_cmp,           \
-               .dup = _dup_value,              \
-               .free = _free_value,            \
-
-       static struct type Tbool = {
-               BaseType
-               .name = "Boolean",
-               .vtype = Vbool,
-       };
-
-       static struct type Tstr = {
-               BaseType
-               .name = "string",
-               .vtype = Vstr,
-       };
-
-       static struct type Tnum = {
-               BaseType
-               .name = "number",
-               .vtype = Vnum,
-       };
-
-       static struct type Tnone = {
-               BaseType
-               .name = "none",
-               .vtype = Vnone,
-       };
+###### context initialization
 
-       static struct type Tlabel = {
-               BaseType
-               .name = "label",
-               .vtype = 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
@@ -797,7 +831,7 @@ 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
@@ -814,7 +848,7 @@ it is constant
 
 Scopes in parallel branches can be partially merged.  More
 specifically, if a given name is declared in both branches of an
-if/else then it's scope is a candidate for merging.  Similarly if
+if/else then its scope is a candidate for merging.  Similarly if
 every branch of an exhaustive switch (e.g. has an "else" clause)
 declares a given name, then the scopes from the branches are
 candidates for merging.
@@ -849,12 +883,12 @@ 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.
 
 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
@@ -890,8 +924,8 @@ like "if" and the code following it.
 ###### Grammar
 
        $void
-       OpenScope -> ${ scope_push(config2context(config)); }$
-
+       OpenScope -> ${ scope_push(c); }$
+       ClosePara -> ${ var_block_close(c, CloseParallel); }$
 
 Each variable records a scope depth and is in one of four states:
 
@@ -924,7 +958,6 @@ Each variable records a scope depth and is in one of four states:
   in scope.  It is permanently out of scope now and can be removed from
   the "in scope" stack.
 
-
 ###### variable fields
        int depth, min_depth;
        enum { OutScope, PendingScope, CondScope, InScope } scope;
@@ -935,12 +968,23 @@ Each variable records a scope depth and is in one of four states:
        struct variable *in_scope;
 
 All variables with the same name are linked together using the
-'previous' link.  Those variable that have
-been affirmatively merged all have a 'merged' pointer that points to
-one primary variable - the most recently declared instance. When
-merging variables, we need to also adjust the 'merged' pointer on any
-other variables that had previously been merged with the one that will
-no longer be primary.
+'previous' link.  Those variable that have been affirmatively merged all
+have a 'merged' pointer that points to one primary variable - the most
+recently declared instance.  When merging variables, we need to also
+adjust the 'merged' pointer on any other variables that had previously
+been merged with the one that will no longer be primary.
+
+A variable that is no longer the most recent instance of a name may
+still have "pending" scope, if it might still be merged with most
+recent instance.  These variables don't really belong in the
+"in_scope" list, but are not immediately removed when a new instance
+is found.  Instead, they are detected and ignored when considering the
+list of in_scope names.
+
+The storage of the value of a variable will be described later.  For now
+we just need to know that when a variable goes out of scope, it might
+need to be freed.  For this we need to be able to find it, so assume that 
+`var_value()` will provide that.
 
 ###### variable fields
        struct variable *merged;
@@ -953,7 +997,7 @@ no longer be primary.
 
                if (primary->merged)
                        // shouldn't happen
-                       primary = primary->merged;
+                       primary = primary->merged;      // NOTEST
 
                for (v = primary->previous; v; v=v->previous)
                        if (v == secondary || v == secondary->merged ||
@@ -964,7 +1008,10 @@ no longer be primary.
                        }
        }
 
-###### free context
+###### forward decls
+       static struct value *var_value(struct parse_context *c, struct variable *v);
+
+###### free context vars
 
        while (context.varlist) {
                struct binding *b = context.varlist;
@@ -975,7 +1022,10 @@ no longer be primary.
                        struct variable *t = v;
 
                        v = t->previous;
-                       free_value(t->val);
+                       free_value(t->type, var_value(&context, t));
+                       if (t->depth == 0)
+                               // This is a global constant
+                               free_exec(t->where_decl);
                        free(t);
                }
        }
@@ -990,21 +1040,23 @@ the latest usage.  This is determined from `min_depth`.  When a
 conditionally visible variable gets affirmed like this, it is also
 merged with other conditionally visible variables with the same name.
 
-When we parse a variable declaration we either signal an error if the
+When we parse a variable declaration we either report an error if the
 name is currently bound, or create a new variable at the current nest
 depth if the name is unbound or bound to a conditionally scoped or
 pending-scope variable.  If the previous variable was conditionally
 scoped, it and its homonyms becomes out-of-scope.
 
-When we parse a variable reference (including non-declarative
-assignment) we signal an error if the name is not bound or is bound to
+When we parse a variable reference (including non-declarative assignment
+"foo = bar") we report an error if the name is not bound or is bound to
 a pending-scope variable; update the scope if the name is bound to a
 conditionally scoped variable; or just proceed normally if the named
 variable is in scope.
 
 When we exit a scope, any variables bound at this level are either
-marked out of scope or pending-scoped, depending on whether the
-scope was sequential or parallel.
+marked out of scope or pending-scoped, depending on whether the scope
+was sequential or parallel.  Here a "parallel" scope means the "then"
+or "else" part of a conditional, or any "case" or "else" branch of a
+switch.  Other scopes are "sequential".
 
 When exiting a parallel scope we check if there are any variables that
 were previously pending and are still visible. If there are, then
@@ -1050,7 +1102,6 @@ all pending-scope variables become conditionally scoped.
                v->scope = InScope;
                v->in_scope = c->in_scope;
                c->in_scope = v;
-               v->val = val_init(NULL);
                return v;
        }
 
@@ -1063,7 +1114,7 @@ all pending-scope variables become conditionally scoped.
                switch (v ? v->scope : OutScope) {
                case OutScope:
                case PendingScope:
-                       /* Signal an error - once that is possible */
+                       /* Caller will report the error */
                        return NULL;
                case CondScope:
                        /* All CondScope variables of this name need to be merged
@@ -1084,14 +1135,14 @@ all pending-scope variables become conditionally scoped.
 
        static void var_block_close(struct parse_context *c, enum closetype ct)
        {
-               /* close of all variables that are in_scope */
+               /* Close off all variables that are in_scope */
                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;
                     ) {
-                       switch (ct) {
+                       if (v->name->var == v) switch (ct) {
                        case CloseElse:
                        case CloseParallel: /* handle PendingScope */
                                switch(v->scope) {
@@ -1102,7 +1153,7 @@ all pending-scope variables become conditionally scoped.
                                        else if (v->previous &&
                                                 v->previous->scope == PendingScope)
                                                v->scope = PendingScope;
-                                       else if (v->val.type == &Tlabel)
+                                       else if (v->type == Tlabel)
                                                v->scope = PendingScope;
                                        else if (v->name->var == v)
                                                v->scope = OutScope;
@@ -1119,14 +1170,14 @@ 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 = OutScope;
                                        break;
                                case OutScope: break;
                                }
                                break;
                        case CloseSequential:
-                               if (v->val.type == &Tlabel)
+                               if (v->type == Tlabel)
                                        v->scope = PendingScope;
                                switch (v->scope) {
                                case InScope:
@@ -1141,7 +1192,7 @@ 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
@@ -1152,24 +1203,131 @@ all pending-scope variables become conditionally scoped.
                                }
                                break;
                        }
-                       if (v->scope == OutScope)
+                       if (v->scope == OutScope || v->name->var != v)
                                *vp = v->in_scope;
                        else
                                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.
+
+###### variable fields
+               short frame_pos;
+               short global;
+
+###### 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;
+                       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);
+
+               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, we need to walk the list of variables
+to find any that weren't merged away and that aren't global, and to
+calculate the frame size and assign a frame position for each variable.
+For this we have `scope_finalize()`.
+
+###### ast functions
+
+       static void scope_finalize(struct parse_context *c)
+       {
+               struct binding *b;
+
+               for (b = c->varlist; b; b = b->next) {
+                       struct variable *v;
+                       for (v = b->var; v; v = v->previous) {
+                               struct type *t = v->type;
+                               if (v->merged && v->merged != v)
+                                       continue;
+                               if (v->global)
+                                       continue;
+                               if (c->local_size & (t->align - 1))
+                                       c->local_size = (c->local_size + t->align) & ~(t->align-1);
+                               v->frame_pos = c->local_size;
+                               c->local_size += v->type->size;
+                       }
+               }
+               c->local = calloc(1, c->local_size);
+       }
+
+###### free context vars
+       free(context.global);
+       free(context.local);
+
 ### 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) ({            \
@@ -1210,33 +1368,34 @@ 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
                if (loc->line >= 0) {
                        fprintf(f, "%d:%d: ", loc->line, loc->column);
                        return 1;
                }
                if (loc->type == Xbinode)
                        return __fput_loc(cast(binode,loc)->left, f) ||
-                              __fput_loc(cast(binode,loc)->right, f);
-               return 0;
+                              __fput_loc(cast(binode,loc)->right, f);  // NOTEST
+               return 0;                       // NOTEST
        }
        static void fput_loc(struct exec *loc, FILE *f)
        {
                if (!__fput_loc(loc, f))
-                       fprintf(f, "??:??: ");
+                       fprintf(f, "??:??: ");  // NOTEST
        }
 
-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
 
 The parser generator requires a `free_foo` function for each struct
-that stores attributes and they will be `exec`s and subtypes there-of.
-So we need `free_exec` which can handle all the subtypes, and we need
-`free_binode`.
+that stores attributes and they will often be `exec`s and subtypes
+there-of.  So we need `free_exec` which can handle all the subtypes,
+and we need `free_binode`.
 
 ###### ast functions
 
@@ -1293,7 +1452,7 @@ also want to know what sort of bracketing to use.
        static void print_exec(struct exec *e, int indent, int bracket)
        {
                if (!e)
-                       return;
+                       return;         // NOTEST
                switch (e->type) {
                case Xbinode:
                        print_binode(cast(binode, e), indent, bracket); break;
@@ -1307,8 +1466,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
@@ -1318,15 +1477,27 @@ by reference. It is set to `0` when an error is found, and `2` when
 any change is made.  If it remains unchanged at `1`, then no more
 propagation is needed.
 
+###### ast
+
+       enum val_rules {Rnolabel = 1<<0, Rboolok = 1<<1, Rnoconstant = 2<<1};
+
+###### format cases
+       case 'r':
+               if (rules & Rnolabel)
+                       fputs(" (labels not permitted)", stderr);
+               break;
+
 ###### core functions
 
        static struct type *propagate_types(struct exec *prog, struct parse_context *c, int *ok,
-                                           struct type *type, int rules)
+                                           struct type *type, int rules);
+       static struct type *__propagate_types(struct exec *prog, struct parse_context *c, int *ok,
+                                             struct type *type, int rules)
        {
                struct type *t;
 
                if (!prog)
-                       return &Tnone;
+                       return Tnone;
 
                switch (prog->type) {
                case Xbinode:
@@ -1339,54 +1510,825 @@ propagation is needed.
                }
                ## propagate exec cases
                }
-               return &Tnone;
+               return Tnone;
+       }
+
+       static struct type *propagate_types(struct exec *prog, struct parse_context *c, int *ok,
+                                           struct type *type, int rules)
+       {
+               struct type *ret = __propagate_types(prog, c, ok, type, rules);
+
+               if (c->parse_error)
+                       *ok = 0;
+               return ret;
        }
 
 #### Interpreting
 
 Interpreting an `exec` doesn't require anything but the `exec`.  State
 is stored in variables and each variable will be directly linked from
-within the `exec` tree.  The exception to this is the 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;
+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
 
-       static struct value interp_exec(struct exec *e)
+       struct lrval {
+               struct type *type;
+               struct value rval, *lval;
+       };
+
+       static struct lrval _interp_exec(struct parse_context *c, struct exec *e);
+
+       static struct value interp_exec(struct parse_context *c, struct exec *e,
+                                       struct type **typeret)
        {
-               struct value rv;
-               rv.type = &Tnone;
-               if (!e)
-                       return rv;
+               struct lrval ret = _interp_exec(c, e);
+
+               if (!ret.type) abort();
+               if (typeret)
+                       *typeret = ret.type;
+               if (ret.lval)
+                       dup_value(ret.type, ret.lval, &ret.rval);
+               return ret.rval;
+       }
+
+       static struct value *linterp_exec(struct parse_context *c, struct exec *e,
+                                         struct type **typeret)
+       {
+               struct lrval ret = _interp_exec(c, e);
+
+               if (ret.lval)
+                       *typeret = ret.type;
+               else
+                       free_value(ret.type, &ret.rval);
+               return ret.lval;
+       }
+
+       static struct lrval _interp_exec(struct parse_context *c, struct exec *e)
+       {
+               struct lrval ret;
+               struct value rv = {}, *lrv = NULL;
+               struct type *rvtype;
+
+               rvtype = ret.type = Tnone;
+               if (!e) {
+                       ret.lval = lrv;
+                       ret.rval = rv;
+                       return ret;
+               }
 
                switch(e->type) {
                case Xbinode:
                {
                        struct binode *b = cast(binode, e);
-                       struct value left, right;
-                       left.type = right.type = &Tnone;
+                       struct value left, right, *lleft;
+                       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
                }
-               return rv;
+               ret.lval = lrv;
+               ret.rval = rv;
+               ret.type = rvtype;
+               return ret;
        }
 
-## Language elements
+### Complex types
 
-Each language element needs to be parsed, printed, analysed,
-interpreted, and freed.  There are several, so let's just start with
-the easy ones and work our way up.
+Now that we have the shape of the interpreter in place we can add some
+complex types and connected them in to the data structures and the
+different phases of parse, analyse, print, interpret.
 
-### Values
+Thus far we have arrays and structs.
 
-We have already met values as separate objects.  When manifest
+#### Arrays
+
+Arrays can be declared by giving a size and a type, as `[size]type' so
+`freq:[26]number` declares `freq` to be an array of 26 numbers.  The
+size can be either a literal number, or a named constant.  Some day an
+arbitrary expression will be supported.
+
+As a formal parameter to a function, the array can be declared with 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 recursive depth.
+
+For now we have two sorts of array, those with fixed size either because
+it is given as a literal number or because it is a struct member (which
+cannot have a runtime-changing size), and those with a size that is
+determined at runtime - local variables with a const size.  The former
+have their size calculated at parse time, the latter at run time.
+
+For the latter type, the `size` field of the type is the size of a
+pointer, and the array is reallocated every time it comes into scope.
+
+We differentiate struct fields with a const size from local variables
+with a const size by whether they are prepared at parse time or not.
+
+###### type union fields
+
+       struct {
+               int unspec;     // size is unspecified - vsize must be set.
+               short size;
+               short static_size;
+               struct variable *vsize;
+               struct type *member;
+       } array;
+
+###### value union fields
+       void *array;  // used if not static_size
+
+###### value functions
+
+       static void array_prepare_type(struct parse_context *c, struct type *type,
+                                      int parse_time)
+       {
+               struct value *vsize;
+               mpz_t q;
+               if (!type->array.vsize || type->array.static_size)
+                       return;
+
+               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 void array_init(struct type *type, struct value *val)
+       {
+               int i;
+               void *ptr = val->ptr;
+
+               if (!val)
+                       return;
+               if (!type->array.static_size) {
+                       val->array = calloc(type->array.size, 
+                                           type->array.member->size);
+                       ptr = val->array;
+               }
+               for (i = 0; i < type->array.size; i++) {
+                       struct value *v;
+                       v = (void*)ptr + i * type->array.member->size;
+                       val_init(type->array.member, v);
+               }
+       }
+
+       static void array_free(struct type *type, struct value *val)
+       {
+               int i;
+               void *ptr = val->ptr;
+
+               if (!type->array.static_size)
+                       ptr = val->array;
+               for (i = 0; i < type->array.size; i++) {
+                       struct value *v;
+                       v = (void*)ptr + i * type->array.member->size;
+                       free_value(type->array.member, v);
+               }
+               if (!type->array.static_size)
+                       free(ptr);
+       }
+
+       static int array_compat(struct type *require, struct type *have)
+       {
+               if (have->compat != require->compat)
+                       return 0;
+               /* Both are arrays, so we can look at details */
+               if (!type_compat(require->array.member, have->array.member, 0))
+                       return 0;
+               if (have->array.unspec && require->array.unspec) {
+                       if (have->array.vsize && require->array.vsize &&
+                           have->array.vsize != require->array.vsize)
+                               /* sizes might not be the same */
+                               return 0;
+                       return 1;
+               }
+               if (have->array.unspec || require->array.unspec)
+                       return 1;
+               if (require->array.vsize == NULL && have->array.vsize == NULL)
+                       return require->array.size == have->array.size;
+
+               return require->array.vsize == have->array.vsize;
+       }
+
+       static void array_print_type(struct type *type, FILE *f)
+       {
+               fputs("[", f);
+               if (type->array.vsize) {
+                       struct binding *b = type->array.vsize->name;
+                       fprintf(f, "%.*s%s]", b->name.len, b->name.txt,
+                               type->array.unspec ? "::" : "");
+               } else
+                       fprintf(f, "%d]", type->array.size);
+               type_print(type->array.member, f);
+       }
+
+       static struct type array_prototype = {
+               .init = array_init,
+               .prepare_type = array_prepare_type,
+               .print_type = array_print_type,
+               .compat = array_compat,
+               .free = array_free,
+               .size = sizeof(void*),
+               .align = sizeof(void*),
+       };
+
+###### declare terminals
+       $TERM [ ]
+
+###### type grammar
+
+       | [ NUMBER ] Type ${ {
+               char tail[3];
+               mpq_t num;
+               struct 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])
+                       tok_err(c, "error: unsupported number suffix", &$2);
+               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);
+                       } else if (mpz_cmp_ui(mpq_numref(num), 1UL << 30) >= 0)
+                               tok_err(c, "error: array size is too large",
+                                       &$2);
+                       mpq_clear(num);
+               }
+               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 = add_type(c, noname, &array_prototype);
+               $0->array.member = $<4;
+               $0->array.size = 0;
+               $0->array.vsize = v;
+       } }$
+
+###### Grammar
+       $*type
+       OptType -> Type ${ $0 = $<1; }$
+               | ${ $0 = NULL; }$
+
+###### formal type grammar
+
+       | [ IDENTIFIER :: OptType ] Type ${ {
+               struct variable *v = var_decl(c, $ID.txt);
+               struct text noname = { "", 0 };
+
+               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,
+
+###### variable grammar
+
+       | Variable [ Expression ] ${ {
+               struct binode *b = new(binode);
+               b->op = Index;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
+       } }$
+
+###### print binode cases
+       case Index:
+               print_exec(b->left, -1, bracket);
+               printf("[");
+               print_exec(b->right, -1, bracket);
+               printf("]");
+               break;
+
+###### propagate binode cases
+       case Index:
+               /* left must be an array, right must be a number,
+                * result is the member type of the array
+                */
+               propagate_types(b->right, c, ok, Tnum, 0);
+               t = propagate_types(b->left, c, ok, NULL, rules & Rnoconstant);
+               if (!t || t->compat != array_compat) {
+                       type_err(c, "error: %1 cannot be indexed", prog, t, 0, NULL);
+                       return NULL;
+               } else {
+                       if (!type_compat(type, t->array.member, rules)) {
+                               type_err(c, "error: have %1 but need %2", prog,
+                                        t->array.member, rules, type);
+                       }
+                       return t->array.member;
+               }
+               break;
+
+###### interp binode cases
+       case Index: {
+               mpz_t q;
+               long i;
+               void *ptr;
+
+               lleft = linterp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               mpz_init(q);
+               mpz_tdiv_q(q, mpq_numref(right.num), mpq_denref(right.num));
+               i = mpz_get_si(q);
+               mpz_clear(q);
+
+               if (ltype->array.static_size)
+                       ptr = lleft;
+               else
+                       ptr = *(void**)lleft;
+               rvtype = ltype->array.member;
+               if (i >= 0 && i < ltype->array.size)
+                       lrv = ptr + i * rvtype->size;
+               else
+                       val_init(ltype->array.member, &rv);
+               ltype = NULL;
+               break;
+       }
+
+#### Structs
+
+A `struct` is a data-type that contains one or more other data-types.
+It differs from an array in that each member can be of a different
+type, and they are accessed by name rather than by number.  Thus you
+cannot choose an element by calculation, you need to know what you
+want up-front.
+
+The language makes no promises about how a given structure will be
+stored in memory - it is free to rearrange fields to suit whatever
+criteria seems important.
+
+Structs are declared separately from program code - they cannot be
+declared in-line in a variable declaration like arrays can.  A struct
+is given a name and this name is used to identify the type - the name
+is not prefixed by the word `struct` as it would be in C.
+
+Structs are only treated as the same if they have the same name.
+Simply having the same fields in the same order is not enough.  This
+might change once we can create structure initializers from a list of
+values.
+
+Each component datum is identified much like a variable is declared,
+with a name, one or two colons, and a type.  The type cannot be omitted
+as there is no opportunity to deduce the type from usage.  An initial
+value can be given following an equals sign, so
+
+##### Example: a struct type
+
+       struct complex:
+               x:number = 0
+               y:number = 0
+
+would declare a type called "complex" which has two number fields,
+each initialised to zero.
+
+Struct will need to be declared separately from the code that uses
+them, so we will need to be able to print out the declaration of a
+struct when reprinting the whole program.  So a `print_type_decl` type
+function will be needed.
+
+###### type union fields
+
+       struct {
+               int nfields;
+               struct field {
+                       struct text name;
+                       struct type *type;
+                       struct value *init;
+                       int offset;
+               } *fields;
+       } structure;
+
+###### type functions
+       void (*print_type_decl)(struct type *type, FILE *f);
+
+###### value functions
+
+       static void structure_init(struct type *type, struct value *val)
+       {
+               int i;
+
+               for (i = 0; i < type->structure.nfields; i++) {
+                       struct value *v;
+                       v = (void*) val->ptr + type->structure.fields[i].offset;
+                       if (type->structure.fields[i].init)
+                               dup_value(type->structure.fields[i].type, 
+                                         type->structure.fields[i].init,
+                                         v);
+                       else
+                               val_init(type->structure.fields[i].type, v);
+               }
+       }
+
+       static void structure_free(struct type *type, struct value *val)
+       {
+               int i;
+
+               for (i = 0; i < type->structure.nfields; i++) {
+                       struct value *v;
+                       v = (void*)val->ptr + type->structure.fields[i].offset;
+                       free_value(type->structure.fields[i].type, v);
+               }
+       }
+
+       static void structure_free_type(struct type *t)
+       {
+               int i;
+               for (i = 0; i < t->structure.nfields; i++)
+                       if (t->structure.fields[i].init) {
+                               free_value(t->structure.fields[i].type,
+                                          t->structure.fields[i].init);
+                       }
+               free(t->structure.fields);
+       }
+
+       static struct type structure_prototype = {
+               .init = structure_init,
+               .free = structure_free,
+               .free_type = structure_free_type,
+               .print_type_decl = structure_print_type,
+       };
+
+###### exec type
+       Xfieldref,
+
+###### ast
+       struct fieldref {
+               struct exec;
+               struct exec *left;
+               int index;
+               struct text name;
+       };
+
+###### free exec cases
+       case Xfieldref:
+               free_exec(cast(fieldref, e)->left);
+               free(e);
+               break;
+
+###### declare terminals
+       $TERM struct .
+
+###### variable grammar
+
+       | Variable . IDENTIFIER ${ {
+               struct fieldref *fr = new_pos(fieldref, $2);
+               fr->left = $<1;
+               fr->name = $3.txt;
+               fr->index = -2;
+               $0 = fr;
+       } }$
+
+###### print exec cases
+
+       case Xfieldref:
+       {
+               struct fieldref *f = cast(fieldref, e);
+               print_exec(f->left, -1, bracket);
+               printf(".%.*s", f->name.len, f->name.txt);
+               break;
+       }
+
+###### ast functions
+       static int find_struct_index(struct type *type, struct text field)
+       {
+               int i;
+               for (i = 0; i < type->structure.nfields; i++)
+                       if (text_cmp(type->structure.fields[i].name, field) == 0)
+                               return i;
+               return -1;
+       }
+
+###### propagate exec cases
+
+       case Xfieldref:
+       {
+               struct fieldref *f = cast(fieldref, prog);
+               struct type *st = propagate_types(f->left, c, ok, NULL, 0);
+
+               if (!st)
+                       type_err(c, "error: unknown type for field access", f->left,
+                                NULL, 0, NULL);
+               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) {
+                       f->index = find_struct_index(st, f->name);
+                       if (f->index < 0)
+                               type_err(c, "error: cannot find requested field in %1",
+                                        f->left, st, 0, NULL);
+               }
+               if (f->index >= 0) {
+                       struct type *ft = st->structure.fields[f->index].type;
+                       if (!type_compat(type, ft, rules))
+                               type_err(c, "error: have %1 but need %2", prog,
+                                        ft, rules, type);
+                       return ft;
+               }
+               break;
+       }
+
+###### interp exec cases
+       case Xfieldref:
+       {
+               struct fieldref *f = cast(fieldref, e);
+               struct type *ltype;
+               struct value *lleft = linterp_exec(c, f->left, &ltype);
+               lrv = (void*)lleft->ptr + ltype->structure.fields[f->index].offset;
+               rvtype = ltype->structure.fields[f->index].type;
+               break;
+       }
+
+###### ast
+       struct fieldlist {
+               struct fieldlist *prev;
+               struct field f;
+       };
+
+###### ast functions
+       static void free_fieldlist(struct fieldlist *f)
+       {
+               if (!f)
+                       return;
+               free_fieldlist(f->prev);
+               if (f->f.init) {
+                       free_value(f->f.type, f->f.init);
+                       free(f->f.init);
+               }
+               free(f);
+       }
+
+###### top level grammar
+       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;
+                       }
+               } }$
+
+       $*fieldlist
+       FieldBlock -> { IN OptNL FieldLines OUT OptNL } ${ $0 = $<FL; }$
+               | { SimpleFieldList } ${ $0 = $<SFL; }$
+               | IN OptNL FieldLines OUT ${ $0 = $<FL; }$
+               | SimpleFieldList EOL ${ $0 = $<SFL; }$
+
+       FieldLines -> SimpleFieldList Newlines ${ $0 = $<SFL; }$
+               | FieldLines SimpleFieldList Newlines ${
+                       $SFL->prev = $<FL;
+                       $0 = $<SFL;
+               }$
+
+       SimpleFieldList -> Field ${ $0 = $<F; }$
+               | SimpleFieldList ; Field ${
+                       $F->prev = $<SFL;
+                       $0 = $<F;
+               }$
+               | SimpleFieldList ; ${
+                       $0 = $<SFL;
+               }$
+               | ERROR ${ tok_err(c, "Syntax error in struct field", &$1); }$
+
+       Field -> IDENTIFIER : Type = Expression ${ {
+                       int ok;
+
+                       $0 = calloc(1, sizeof(struct fieldlist));
+                       $0->f.name = $1.txt;
+                       $0->f.type = $<3;
+                       $0->f.init = NULL;
+                       do {
+                               ok = 1;
+                               propagate_types($<5, c, &ok, $3, 0);
+                       } while (ok == 2);
+                       if (!ok)
+                               c->parse_error = 1;
+                       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;
+                       if ($0->f.type->prepare_type)
+                               $0->f.type->prepare_type(c, $0->f.type, 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;
+
+               fprintf(f, "struct %.*s\n", t->name.len, t->name.txt);
+
+               for (i = 0; i < t->structure.nfields; i++) {
+                       struct field *fl = t->structure.fields + i;
+                       fprintf(f, "    %.*s : ", fl->name.len, fl->name.txt);
+                       type_print(fl->type, f);
+                       if (fl->type->print && fl->init) {
+                               fprintf(f, " = ");
+                               if (fl->type == Tstr)
+                                       fprintf(f, "\"");
+                               print_value(fl->type, fl->init);
+                               if (fl->type == Tstr)
+                                       fprintf(f, "\"");
+                       }
+                       printf("\n");
+               }
+       }
+
+###### print type decls
+       {
+               struct type *t;
+               int target = -1;
+
+               while (target != 0) {
+                       int i = 0;
+                       for (t = context.typelist; t ; t=t->next)
+                               if (t->print_type_decl) {
+                                       i += 1;
+                                       if (i == target)
+                                               break;
+                               }
+
+                       if (target == -1) {
+                               target = i;
+                       } else {
+                               t->print_type_decl(t, stdout);
+                               target -= 1;
+                       }
+               }
+       }
+
+### Functions
+
+A function is a named chunk of code which can be passed parameters and
+can return results.  Each function has an implicit type which includes
+the set of parameters and the return value.  As yet these types cannot
+be declared separate from the function itself.
+
+In fact, only one function is currently possible - `main`.  `main` is
+passed an array of strings together with the size of the array, and
+doesn't return anything.  The strings are command line arguments.
+
+The parameters can be specified either in parentheses as a list, such as
+
+##### Example: function 1
+
+       func main(av:[ac::number]string)
+               code block
+
+or as an indented list of one parameter per line
+
+##### Example: function 2
+
+       func main
+               argv:[argc::number]string
+       do
+               code block
+
+###### Binode types
+       Func, List,
+
+###### Grammar
+
+       $TERM func main
+
+       $*binode
+       MainFunction -> func main ( OpenScope Args ) Block Newlines ${
+                       $0 = new(binode);
+                       $0->op = Func;
+                       $0->left = reorder_bilist($<Ar);
+                       $0->right = $<Bl;
+                       var_block_close(c, CloseSequential);
+                       if (c->scope_stack && !c->parse_error) abort();
+               }$
+               | func main IN OpenScope OptNL Args OUT OptNL do Block Newlines ${
+                       $0 = new(binode);
+                       $0->op = Func;
+                       $0->left = reorder_bilist($<Ar);
+                       $0->right = $<Bl;
+                       var_block_close(c, CloseSequential);
+                       if (c->scope_stack && !c->parse_error) abort();
+               }$
+               | func main NEWLINE OpenScope OptNL do Block Newlines ${
+                       $0 = new(binode);
+                       $0->op = Func;
+                       $0->left = NULL;
+                       $0->right = $<Bl;
+                       var_block_close(c, CloseSequential);
+                       if (c->scope_stack && !c->parse_error) abort();
+               }$
+
+       Args -> ${ $0 = NULL; }$
+               | Varlist ${ $0 = $<1; }$
+               | Varlist ; ${ $0 = $<1; }$
+               | Varlist NEWLINE ${ $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,
+interpreted, and freed.  There are several, so let's just start with
+the easy ones and work our way up.
+
+### Values
+
+We have already met values as separate objects.  When manifest
 constants appear in the program text, that must result in an executable
 which has a constant value.  So the `val` structure embeds a value in
 an executable.
@@ -1397,53 +2339,59 @@ 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);
                                if (tail[0])
-                                       tok_err(config2context(config), "error: unsupported number suffix.",
+                                       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);
                        if (tail[0])
-                               tok_err(config2context(config), "error: unsupported string suffix.",
+                               tok_err(c, "error: unsupported string suffix",
                                        &$1);
                        }
                        }$
                | 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);
                        if (tail[0])
-                               tok_err(config2context(config), "error: unsupported string suffix.",
+                               tok_err(c, "error: unsupported string suffix",
                                        &$1);
                        }
                        }$
@@ -1452,36 +2400,35 @@ 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;
        }
 
 ###### propagate exec cases
-               case Xval:
-               {
-                       struct val *val = cast(val, prog);
-                       if (!vtype_compat(type, val->val.type, rules)) {
-                               type_err(c, "error: expected %1%r found %2",
-                                          prog, type, rules, val->val.type);
-                               *ok = 0;
-                       }
-                       return val->val.type;
-               }
+       case Xval:
+       {
+               struct val *val = cast(val, prog);
+               if (!type_compat(type, val->vtype, rules))
+                       type_err(c, "error: expected %1%r found %2",
+                                  prog, type, rules, val->vtype);
+               return val->vtype;
+       }
 
 ###### interp exec cases
        case Xval:
-               return 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);
        }
 
@@ -1489,7 +2436,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)
@@ -1513,7 +2460,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.
@@ -1529,53 +2476,92 @@ link to find the primary instance.
 
 ###### Grammar
 
+       $TERM : ::
+
        $*var
-       VariableDecl -> IDENTIFIER := ${ {
-               struct variable *v = var_decl(config2context(config), $1.txt);
+       VariableDecl -> IDENTIFIER : ${ {
+               struct variable *v = var_decl(c, $1.txt);
                $0 = new_pos(var, $1);
                $0->var = v;
                if (v)
                        v->where_decl = $0;
                else {
-                       v = var_ref(config2context(config), $1.txt);
+                       v = var_ref(c, $1.txt);
                        $0->var = v;
-                       type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, &Tnone, 0, &Tnone);
-                       type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, &Tnone, 0, &Tnone);
+                       type_err(c, "error: variable '%v' redeclared",
+                                $0, NULL, 0, NULL);
+                       type_err(c, "info: this is where '%v' was first declared",
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
-           | IDENTIFIER ::= ${ {
-               struct variable *v = var_decl(config2context(config), $1.txt);
+           | IDENTIFIER :: ${ {
+               struct variable *v = var_decl(c, $1.txt);
                $0 = new_pos(var, $1);
                $0->var = v;
                if (v) {
                        v->where_decl = $0;
                        v->constant = 1;
                } else {
-                       v = var_ref(config2context(config), $1.txt);
+                       v = var_ref(c, $1.txt);
                        $0->var = v;
-                       type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, &Tnone, 0, &Tnone);
-                       type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, &Tnone, 0, &Tnone);
+                       type_err(c, "error: variable '%v' redeclared",
+                                $0, NULL, 0, NULL);
+                       type_err(c, "info: this is where '%v' was first declared",
+                                v->where_decl, NULL, 0, NULL);
+               }
+       } }$
+           | IDENTIFIER : Type ${ {
+               struct variable *v = var_decl(c, $1.txt);
+               $0 = new_pos(var, $1);
+               $0->var = v;
+               if (v) {
+                       v->where_decl = $0;
+                       v->where_set = $0;
+                       v->type = $<Type;
+               } else {
+                       v = var_ref(c, $1.txt);
+                       $0->var = v;
+                       type_err(c, "error: variable '%v' redeclared",
+                                $0, NULL, 0, NULL);
+                       type_err(c, "info: this is where '%v' was first declared",
+                                v->where_decl, NULL, 0, NULL);
+               }
+       } }$
+           | IDENTIFIER :: Type ${ {
+               struct variable *v = var_decl(c, $1.txt);
+               $0 = new_pos(var, $1);
+               $0->var = v;
+               if (v) {
+                       v->where_decl = $0;
+                       v->where_set = $0;
+                       v->type = $<Type;
+                       v->constant = 1;
+               } else {
+                       v = var_ref(c, $1.txt);
+                       $0->var = v;
+                       type_err(c, "error: variable '%v' redeclared",
+                                $0, NULL, 0, NULL);
+                       type_err(c, "info: this is where '%v' was first declared",
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
 
+       $*exec
        Variable -> IDENTIFIER ${ {
-               struct variable *v = var_ref(config2context(config), $1.txt);
+               struct variable *v = var_ref(c, $1.txt);
                $0 = new_pos(var, $1);
                if (v == NULL) {
                        /* This might be a label - allocate a var just in case */
-                       v = var_decl(config2context(config), $1.txt);
+                       v = var_decl(c, $1.txt);
                        if (v) {
-                               v->val = val_init(&Tlabel);
-                               v->val.label = &v->val;
+                               v->type = Tnone;
+                               v->where_decl = $0;
                                v->where_set = $0;
                        }
                }
-               $0->var = v;
+               cast(var, $0)->var = v;
        } }$
+       ## variable grammar
 
 ###### print exec cases
        case Xvar:
@@ -1590,15 +2576,15 @@ 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;
                                fprintf(stderr, "%.*s", b->name.len, b->name.txt);
                        } else
-                               fputs("???", stderr);
+                               fputs("???", stderr);   // NOTEST
                } else
-                       fputs("NOTVAR", stderr);
+                       fputs("NOTVAR", stderr);        // NOTEST
                break;
 
 ###### propagate exec cases
@@ -1608,29 +2594,37 @@ link to find the primary instance.
                struct var *var = cast(var, prog);
                struct variable *v = var->var;
                if (!v) {
-                       type_err(c, "%d:BUG: no variable!!", prog, &Tnone, 0, &Tnone);
-                       *ok = 0;
-                       return &Tnone;
+                       type_err(c, "%d:BUG: no variable!!", prog, NULL, 0, NULL); // NOTEST
+                       return Tnone;                                   // NOTEST
                }
                if (v->merged)
                        v = v->merged;
-               if (v->val.type == NULL) {
+               if (v->constant && (rules & Rnoconstant)) {
+                       type_err(c, "error: Cannot assign to a constant: %v",
+                                prog, NULL, 0, NULL);
+                       type_err(c, "info: name was defined as a constant here",
+                                v->where_decl, NULL, 0, NULL);
+                       return v->type;
+               }
+               if (v->type == Tnone && v->where_decl == prog)
+                       type_err(c, "error: variable used but not declared: %v",
+                                prog, NULL, 0, NULL);
+               if (v->type == NULL) {
                        if (type && *ok != 0) {
-                               v->val = val_init(type);
+                               v->type = type;
                                v->where_set = prog;
                                *ok = 2;
                        }
                        return type;
                }
-               if (!vtype_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, &Tnone);
-                       *ok = 0;
+                                v->type, rules, NULL);
                }
                if (!type)
-                       return v->val.type;
+                       return v->type;
                return type;
        }
 
@@ -1642,7 +2636,9 @@ link to find the primary instance.
 
                if (v->merged)
                        v = v->merged;
-               return dup_value(v->val);
+               lrv = var_value(c, v);
+               rvtype = v->type;
+               break;
        }
 
 ###### ast functions
@@ -1655,104 +2651,222 @@ link to find the primary instance.
 ###### free exec cases
        case Xvar: free_var(cast(var, e)); break;
 
+### Expressions: Conditional
+
+Our first user of the `binode` will be conditional expressions, which
+is a bit odd as they actually have three components.  That will be
+handled by having 2 binodes for each expression.  The conditional
+expression is the lowest precedence operator which is why we define it
+first - to start the precedence list.
+
+Conditional expressions are of the form "value `if` condition `else`
+other_value".  They associate to the right, so everything to the right
+of `else` is part of an else value, while only a higher-precedence to
+the left of `if` is the if values.  Between `if` and `else` there is no
+room for ambiguity, so a full conditional expression is allowed in
+there.
+
+###### Binode types
+       CondExpr,
+
+###### Grammar
+
+       $LEFT if $$ifelse
+       ## expr precedence
+
+       $*exec
+       Expression -> Expression if Expression else Expression $$ifelse ${ {
+                       struct binode *b1 = new(binode);
+                       struct binode *b2 = new(binode);
+                       b1->op = CondExpr;
+                       b1->left = $<3;
+                       b1->right = b2;
+                       b2->op = CondExpr;
+                       b2->left = $<1;
+                       b2->right = $<5;
+                       $0 = b1;
+               } }$
+               ## expression grammar
+
+###### print binode cases
+
+       case CondExpr:
+               b2 = cast(binode, b->right);
+               if (bracket) printf("(");
+               print_exec(b2->left, -1, bracket);
+               printf(" if ");
+               print_exec(b->left, -1, bracket);
+               printf(" else ");
+               print_exec(b2->right, -1, bracket);
+               if (bracket) printf(")");
+               break;
+
+###### propagate binode cases
+
+       case CondExpr: {
+               /* cond must be Tbool, others must match */
+               struct binode *b2 = cast(binode, b->right);
+               struct type *t2;
+
+               propagate_types(b->left, c, ok, Tbool, 0);
+               t = propagate_types(b2->left, c, ok, type, Rnolabel);
+               t2 = propagate_types(b2->right, c, ok, type ?: t, Rnolabel);
+               return t ?: t2;
+       }
+
+###### interp binode cases
+
+       case CondExpr: {
+               struct binode *b2 = cast(binode, b->right);
+               left = interp_exec(c, b->left, &ltype);
+               if (left.bool)
+                       rv = interp_exec(c, b2->left, &rvtype);
+               else
+                       rv = interp_exec(c, b2->right, &rvtype);
+               }
+               break;
+
 ### Expressions: Boolean
 
-Our first user of the `binode` will be expressions, and particularly
-Boolean expressions.  As I haven't implemented precedence in the
-parser generator yet, we need different names from each precedence
-level used by expressions.  The outer most or lowest level precedence
-are Boolean `or` `and`, and `not` which form an `Expression` out of `BTerm`s
-and `BFact`s.
+The next class of expressions to use the `binode` will be Boolean
+expressions.  "`and 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,
+       AndThen,
        Or,
+       OrElse,
        Not,
 
-####### Grammar
+###### expr precedence
+       $LEFT or
+       $LEFT and
+       $LEFT not
 
-       $*exec
-       Expression -> Expression or BTerm ${ {
+###### expression grammar
+               | Expression or Expression ${ {
                        struct binode *b = new(binode);
                        b->op = Or;
                        b->left = $<1;
                        b->right = $<3;
                        $0 = b;
                } }$
-               | BTerm ${ $0 = $<1; }$
+               | Expression or else Expression ${ {
+                       struct binode *b = new(binode);
+                       b->op = OrElse;
+                       b->left = $<1;
+                       b->right = $<4;
+                       $0 = b;
+               } }$
 
-       BTerm -> BTerm and BFact ${ {
+               | Expression and Expression ${ {
                        struct binode *b = new(binode);
                        b->op = And;
                        b->left = $<1;
                        b->right = $<3;
                        $0 = b;
                } }$
-               | BFact ${ $0 = $<1; }$
+               | Expression and then Expression ${ {
+                       struct binode *b = new(binode);
+                       b->op = AndThen;
+                       b->left = $<1;
+                       b->right = $<4;
+                       $0 = b;
+               } }$
 
-       BFact -> not BFact ${ {
+               | not Expression ${ {
                        struct binode *b = new(binode);
                        b->op = Not;
                        b->right = $<2;
                        $0 = b;
                } }$
-               ## other BFact
 
 ###### print binode cases
        case And:
-               print_exec(b->left, -1, 0);
+               if (bracket) printf("(");
+               print_exec(b->left, -1, bracket);
                printf(" and ");
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
+               break;
+       case AndThen:
+               if (bracket) printf("(");
+               print_exec(b->left, -1, bracket);
+               printf(" and then ");
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
                break;
        case Or:
-               print_exec(b->left, -1, 0);
+               if (bracket) printf("(");
+               print_exec(b->left, -1, bracket);
                printf(" or ");
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
+               break;
+       case OrElse:
+               if (bracket) printf("(");
+               print_exec(b->left, -1, bracket);
+               printf(" or else ");
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
                break;
        case Not:
+               if (bracket) printf("(");
                printf("not ");
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
                break;
 
 ###### propagate binode cases
        case And:
+       case AndThen:
        case Or:
+       case OrElse:
        case Not:
                /* both must be Tbool, result is Tbool */
-               propagate_types(b->left, c, ok, &Tbool, 0);
-               propagate_types(b->right, c, ok, &Tbool, 0);
-               if (type && type != &Tbool) {
+               propagate_types(b->left, c, ok, Tbool, 0);
+               propagate_types(b->right, c, ok, Tbool, 0);
+               if (type && type != Tbool)
                        type_err(c, "error: %1 operation found where %2 expected", prog,
-                                  &Tbool, 0, type);
-                       *ok = 0;
-               }
-               return &Tbool;
+                                  Tbool, 0, type);
+               return Tbool;
 
 ###### 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(c, b->left, &rvtype);
+               if (rv.bool)
+                       rv = interp_exec(c, b->right, NULL);
+               break;
        case Or:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                rv.bool = rv.bool || right.bool;
                break;
+       case OrElse:
+               rv = interp_exec(c, b->left, &rvtype);
+               if (!rv.bool)
+                       rv = interp_exec(c, b->right, NULL);
+               break;
        case Not:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                rv.bool = !rv.bool;
                break;
 
 ### Expressions: Comparison
 
-Of slightly higher precedence that Boolean expressions are
-Comparisons.
-A comparison takes arguments of any type, but the two types must be
-the same.
+Of slightly higher precedence that Boolean expressions are Comparisons.
+A comparison takes arguments of any comparable type, but the two types
+must be the same.
 
 To simplify the parsing we introduce an `eop` which can record an
-expression operator.
+expression operator, and the `CMPop` non-terminal will match one of them.
 
 ###### ast
        struct eop {
@@ -1774,15 +2888,17 @@ expression operator.
        Eql,
        NEql,
 
-###### other BFact
-       | Expr CMPop Expr ${ {
-                       struct binode *b = new(binode);
-                       b->op = $2.op;
-                       b->left = $<1;
-                       b->right = $<3;
-                       $0 = b;
+###### expr precedence
+       $LEFT < > <= >= == != CMPop
+
+###### expression grammar
+       | Expression CMPop Expression ${ {
+               struct binode *b = new(binode);
+               b->op = $2.op;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
        } }$
-       | Expr ${ $0 = $<1; }$
 
 ###### Grammar
 
@@ -1802,7 +2918,8 @@ expression operator.
        case GtrEq:
        case Eql:
        case NEql:
-               print_exec(b->left, -1, 0);
+               if (bracket) printf("(");
+               print_exec(b->left, -1, bracket);
                switch(b->op) {
                case Less:   printf(" < "); break;
                case LessEq: printf(" <= "); break;
@@ -1810,9 +2927,10 @@ expression operator.
                case GtrEq:  printf(" >= "); break;
                case Eql:    printf(" == "); break;
                case NEql:   printf(" != "); break;
-               default: abort();
+               default: abort();               // NOTEST
                }
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
+               if (bracket) printf(")");
                break;
 
 ###### propagate binode cases
@@ -1822,7 +2940,7 @@ expression operator.
        case GtrEq:
        case Eql:
        case NEql:
-               /* Both must match but not labels, result is Tbool */
+               /* Both must match but not be labels, result is Tbool */
                t = propagate_types(b->left, c, ok, NULL, Rnolabel);
                if (t)
                        propagate_types(b->right, c, ok, t, 0);
@@ -1831,12 +2949,10 @@ expression operator.
                        if (t)
                                t = propagate_types(b->left, c, ok, t, 0);
                }
-               if (!vtype_compat(type, &Tbool, 0)) {
+               if (!type_compat(type, Tbool, 0))
                        type_err(c, "error: Comparison returns %1 but %2 expected", prog,
-                                   &Tbool, rules, type);
-                       *ok = 0;
-               }
-               return &Tbool;
+                                   Tbool, rules, type);
+               return Tbool;
 
 ###### interp binode cases
        case Less:
@@ -1847,10 +2963,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;
@@ -1858,60 +2974,67 @@ expression operator.
                case GtrEq:     rv.bool = cmp >= 0; break;
                case Eql:       rv.bool = cmp == 0; break;
                case NEql:      rv.bool = cmp != 0; break;
-               default: rv.bool = 0; break;
+               default:        rv.bool = 0; break;     // NOTEST
                }
                break;
        }
 
 ### Expressions: The rest
 
-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 make it easy to reproduce these when printing.  Once
-precedence is handled better I might be able to discard this.
+found.  This makes it easy to reproduce these when printing.  Possibly I
+should only insert brackets were needed for precedence.
 
 ###### Binode types
        Plus, Minus,
-       Times, Divide,
+       Times, Divide, Rem,
        Concat,
        Absolute, Negate,
+       StringConv,
        Bracket,
 
-###### Grammar
+###### expr precedence
+       $LEFT + - Eop
+       $LEFT * / % ++ Top
+       $LEFT Uop $
+       $TERM ( )
 
-       $*exec
-       Expr -> Expr Eop Term ${ {
+###### expression grammar
+               | Expression Eop Expression ${ {
                        struct binode *b = new(binode);
                        b->op = $2.op;
                        b->left = $<1;
                        b->right = $<3;
                        $0 = b;
                } }$
-               | Term ${ $0 = $<1; }$
 
-       Term -> Term Top Factor ${ {
+               | Expression Top Expression ${ {
                        struct binode *b = new(binode);
                        b->op = $2.op;
                        b->left = $<1;
                        b->right = $<3;
                        $0 = b;
                } }$
-               | Factor ${ $0 = $<1; }$
 
-       Factor -> ( Expression ) ${ {
+               | ( Expression ) ${ {
                        struct binode *b = new_pos(binode, $1);
                        b->op = Bracket;
                        b->right = $<2;
                        $0 = b;
                } }$
-               | Uop Factor ${ {
+               | Uop Expression ${ {
                        struct binode *b = new(binode);
                        b->op = $1.op;
                        b->right = $<2;
@@ -1926,9 +3049,11 @@ precedence is handled better I might be able to discard this.
 
        Uop ->    + ${ $0.op = Absolute; }$
                | - ${ $0.op = Negate; }$
+               | $ ${ $0.op = StringConv; }$
 
        Top ->    * ${ $0.op = Times; }$
                | / ${ $0.op = Divide; }$
+               | % ${ $0.op = Rem; }$
                | ++ ${ $0.op = Concat; }$
 
 ###### print binode cases
@@ -1937,28 +3062,37 @@ precedence is handled better I might be able to discard this.
        case Times:
        case Divide:
        case Concat:
-               print_exec(b->left, indent, 0);
+       case Rem:
+               if (bracket) printf("(");
+               print_exec(b->left, indent, bracket);
                switch(b->op) {
-               case Plus:   printf(" + "); break;
-               case Minus:  printf(" - "); break;
-               case Times:  printf(" * "); break;
-               case Divide: printf(" / "); break;
-               case Concat: printf(" ++ "); break;
-               default: abort();
-               }
-               print_exec(b->right, indent, 0);
+               case Plus:   fputs(" + ", stdout); break;
+               case Minus:  fputs(" - ", stdout); break;
+               case Times:  fputs(" * ", stdout); break;
+               case Divide: fputs(" / ", stdout); break;
+               case Rem:    fputs(" % ", stdout); break;
+               case Concat: fputs(" ++ ", stdout); break;
+               default: abort();       // NOTEST
+               }                       // NOTEST
+               print_exec(b->right, indent, bracket);
+               if (bracket) printf(")");
                break;
        case Absolute:
-               printf("+");
-               print_exec(b->right, indent, 0);
-               break;
        case Negate:
-               printf("-");
-               print_exec(b->right, indent, 0);
+       case StringConv:
+               if (bracket) 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;
        case Bracket:
                printf("(");
-               print_exec(b->right, indent, 0);
+               print_exec(b->right, indent, bracket);
                printf(")");
                break;
 
@@ -1966,31 +3100,37 @@ precedence is handled better I might be able to discard this.
        case Plus:
        case Minus:
        case Times:
+       case Rem:
        case Divide:
                /* both must be numbers, result is Tnum */
        case Absolute:
        case Negate:
                /* as propagate_types ignores a NULL,
                 * unary ops fit here too */
-               propagate_types(b->left, c, ok, &Tnum, 0);
-               propagate_types(b->right, c, ok, &Tnum, 0);
-               if (!vtype_compat(type, &Tnum, 0)) {
+               propagate_types(b->left, c, ok, Tnum, 0);
+               propagate_types(b->right, c, ok, Tnum, 0);
+               if (!type_compat(type, Tnum, 0))
                        type_err(c, "error: Arithmetic returns %1 but %2 expected", prog,
-                                  &Tnum, rules, type);
-                       *ok = 0;
-               }
-               return &Tnum;
+                                  Tnum, rules, type);
+               return Tnum;
 
        case Concat:
                /* both must be Tstr, result is Tstr */
-               propagate_types(b->left, c, ok, &Tstr, 0);
-               propagate_types(b->right, c, ok, &Tstr, 0);
-               if (!vtype_compat(type, &Tstr, 0)) {
+               propagate_types(b->left, c, ok, Tstr, 0);
+               propagate_types(b->right, c, ok, Tstr, 0);
+               if (!type_compat(type, Tstr, 0))
                        type_err(c, "error: Concat returns %1 but %2 expected", prog,
-                                  &Tstr, rules, type);
-                       *ok = 0;
-               }
-               return &Tstr;
+                                  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,
+                         "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);
@@ -1998,48 +3138,96 @@ precedence is handled better I might be able to discard this.
 ###### interp binode cases
 
        case Plus:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_add(rv.num, rv.num, right.num);
                break;
        case Minus:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_sub(rv.num, rv.num, right.num);
                break;
        case Times:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_mul(rv.num, rv.num, right.num);
                break;
        case Divide:
-               rv = interp_exec(b->left);
-               right = interp_exec(b->right);
+               rv = interp_exec(c, b->left, &rvtype);
+               right = interp_exec(c, b->right, &rtype);
                mpq_div(rv.num, rv.num, right.num);
                break;
+       case Rem: {
+               mpz_t l, r, rem;
+
+               left = interp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               mpz_init(l); mpz_init(r); mpz_init(rem);
+               mpz_tdiv_q(l, mpq_numref(left.num), mpq_denref(left.num));
+               mpz_tdiv_q(r, mpq_numref(right.num), mpq_denref(right.num));
+               mpz_tdiv_r(rem, l, r);
+               val_init(Tnum, &rv);
+               mpq_set_z(rv.num, rem);
+               mpz_clear(r); mpz_clear(l); mpz_clear(rem);
+               rvtype = ltype;
+               break;
+       }
        case Negate:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                mpq_neg(rv.num, rv.num);
                break;
        case Absolute:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                mpq_abs(rv.num, rv.num);
                break;
        case Bracket:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                break;
        case Concat:
-               left = interp_exec(b->left);
-               right = interp_exec(b->right);
-               rv.type = &Tstr;
+               left = interp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               rvtype = Tstr;
                rv.str = text_join(left.str, right.str);
                break;
+       case StringConv:
+               right = interp_exec(c, b->right, &rvtype);
+               rtype = Tstr;
+               rvtype = Tnum;
+
+               struct text tx = right.str;
+               char tail[3];
+               int neg = 0;
+               if (tx.txt[0] == '-') {
+                       neg = 1;
+                       tx.txt++;
+                       tx.len--;
+               }
+               if (number_parse(rv.num, tail, tx) == 0)
+                       mpq_init(rv.num);
+               else if (neg)
+                       mpq_neg(rv.num, rv.num);
+               if (tail[0])
+                       printf("Unsupported suffix: %.*s\n", tx.len, tx.txt);
+
+               break;
+
+###### value functions
+
+       static struct text text_join(struct text a, struct text b)
+       {
+               struct text rv;
+               rv.len = a.len + b.len;
+               rv.txt = malloc(rv.len);
+               memcpy(rv.txt, a.txt, a.len);
+               memcpy(rv.txt+a.len, b.txt, b.len);
+               return rv;
+       }
 
 ### Blocks, Statements, and Statement lists.
 
 Now that we have expressions out of the way we need to turn to
 statements.  There are simple statements and more complex statements.
-Simple statements do not contain newlines, complex statements do.
+Simple statements do not contain (syntactic) newlines, complex statements do.
 
 Statements often come in sequences and we have corresponding simple
 statement lists and complex statement lists.
@@ -2063,7 +3251,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.
 
@@ -2085,51 +3273,68 @@ and a list.  So we need a function to re-order a list.
 
 The only stand-alone statement we introduce at this stage is `pass`
 which does nothing and is represented as a `NULL` pointer in a `Block`
-list.
+list.  Other stand-alone statements will follow once the infrastructure
+is in-place.
 
 ###### Binode types
        Block,
 
 ###### Grammar
 
-       $void
-       OptNL -> Newlines
-               |
-
-       Newlines -> NEWLINE
-               | Newlines NEWLINE
+       $TERM { } ;
 
        $*binode
-       Open -> {
-               | NEWLINE {
-       Close -> }
-               | NEWLINE }
-       Block -> Open Statementlist Close ${ $0 = $<2; }$
-               | Open Newlines Statementlist Close ${ $0 = $<3; }$
-               | Open SimpleStatements } ${ $0 = reorder_bilist($<2); }$
-               | Open Newlines SimpleStatements } ${ $0 = reorder_bilist($<3); }$
-               | : Statementlist ${ $0 = $<2; }$
-               | : SimpleStatements ${ $0 = reorder_bilist($<2); }$
-
-       Statementlist -> ComplexStatements ${ $0 = reorder_bilist($<1); }$
+       Block -> { IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+               | { SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+               | SimpleStatements ; ${ $0 = reorder_bilist($<SS); }$
+               | SimpleStatements EOL ${ $0 = reorder_bilist($<SS); }$
+               | IN OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       OpenBlock -> OpenScope { IN OptNL Statementlist OUT OptNL } ${ $0 = $<Sl; }$
+               | OpenScope { SimpleStatements } ${ $0 = reorder_bilist($<SS); }$
+               | OpenScope SimpleStatements ; ${ $0 = reorder_bilist($<SS); }$
+               | OpenScope SimpleStatements EOL ${ $0 = reorder_bilist($<SS); }$
+               | IN OpenScope OptNL Statementlist OUT ${ $0 = $<Sl; }$
+
+       UseBlock -> { 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 ${
-               $0 = new(binode);
-               $0->op = Block;
-               $0->left = $<1;
-               $0->right = $<2;
+                       if ($2 == NULL) {
+                               $0 = $<1;
+                       } else {
+                               $0 = new(binode);
+                               $0->op = Block;
+                               $0->left = $<1;
+                               $0->right = $<2;
+                       }
                }$
-               | ComplexStatements NEWLINE ${ $0 = $<1; }$
                | ComplexStatement ${
-               $0 = new(binode);
-               $0->op = Block;
-               $0->left = NULL;
-               $0->right = $<1;
+                       if ($1 == NULL) {
+                               $0 = NULL;
+                       } else {
+                               $0 = new(binode);
+                               $0->op = Block;
+                               $0->left = NULL;
+                               $0->right = $<1;
+                       }
                }$
 
        $*exec
-       ComplexStatement -> SimpleStatements NEWLINE ${
-                       $0 = reorder_bilist($<1);
+       ComplexStatement -> SimpleStatements Newlines ${
+                       $0 = reorder_bilist($<SS);
+                       }$
+               |  SimpleStatements ; Newlines ${
+                       $0 = reorder_bilist($<SS);
                        }$
                ## ComplexStatement Grammar
 
@@ -2146,9 +3351,10 @@ list.
                        $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
 
 ###### print binode cases
@@ -2158,10 +3364,10 @@ list.
                        if (b->left == NULL)
                                printf("pass");
                        else
-                               print_exec(b->left, indent, 0);
+                               print_exec(b->left, indent, bracket);
                        if (b->right) {
                                printf("; ");
-                               print_exec(b->right, indent, 0);
+                               print_exec(b->right, indent, bracket);
                        }
                } else {
                        // block, one per line
@@ -2177,7 +3383,7 @@ list.
 ###### propagate binode cases
        case Block:
        {
-               /* If any statement returns something other then Tnone
+               /* If any statement returns something other than Tnone
                 * or Tbool then all such must return same type.
                 * As each statement may be Tnone or something else,
                 * we must always pass NULL (unknown) down, otherwise an incorrect
@@ -2188,16 +3394,14 @@ list.
 
                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 = NULL;
-                       if (t && t != &Tnone && t != &Tbool) {
+                       if (t && t != Tnone && t != Tbool) {
                                if (!type)
                                        type = t;
-                               else if (t != type) {
+                               else if (t != type)
                                        type_err(c, "error: expected %1%r, found %2",
                                                 e->left, type, rules, t);
-                                       *ok = 0;
-                               }
                        }
                }
                return type;
@@ -2205,10 +3409,10 @@ list.
 
 ###### interp binode cases
        case Block:
-               while (rv.type == &Tnone &&
+               while (rvtype == Tnone &&
                       b) {
                        if (b->left)
-                               rv = interp_exec(b->left);
+                               rv = interp_exec(c, b->left, &rvtype);
                        b = cast(binode, b->right);
                }
                break;
@@ -2225,6 +3429,9 @@ same solution.
 ###### Binode types
        Print,
 
+##### expr precedence
+       $TERM print ,
+
 ###### SimpleStatement Grammar
 
        | print ExpressionList ${
@@ -2266,7 +3473,7 @@ same solution.
                while (b) {
                        if (b->left) {
                                printf(" ");
-                               print_exec(b->left, -1, 0);
+                               print_exec(b->left, -1, bracket);
                                if (b->right)
                                        printf(",");
                        }
@@ -2294,14 +3501,14 @@ same solution.
                        if (b->left) {
                                if (sep)
                                        putchar(sep);
-                               left = interp_exec(b->left);
-                               print_value(left);
-                               free_value(left);
+                               left = interp_exec(c, b->left, &ltype);
+                               print_value(ltype, &left);
+                               free_value(ltype, &left);
                                if (b->right)
                                        sep = ' ';
                        } else if (sep)
                                eol = 0;
-               left.type = &Tnone;
+               ltype = Tnone;
                if (eol)
                        printf("\n");
                break;
@@ -2310,7 +3517,7 @@ same solution.
 ###### 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
@@ -2321,80 +3528,136 @@ it is declared, and error will be raised as the name is created as
        Assign,
        Declare,
 
-###### SimpleStatement Grammar
-       | Variable = Expression ${ {
-                       struct var *v = cast(var, $1);
+###### declare terminals
+       $TERM =
 
+###### SimpleStatement Grammar
+       | Variable = Expression ${
                        $0 = new(binode);
                        $0->op = Assign;
                        $0->left = $<1;
                        $0->right = $<3;
-                       if (v->var && !v->var->constant) {
-                               /* FIXME error? */
-                       }
-               } }$
-       | VariableDecl Expression ${
+               }$
+       | VariableDecl = Expression ${
                        $0 = new(binode);
                        $0->op = Declare;
                        $0->left = $<1;
-                       $0->right =$<2;
+                       $0->right =$<3;
+               }$
+
+       | VariableDecl ${
+                       if ($1->var->where_set == NULL) {
+                               type_err(c,
+                                        "Variable declared with no type or value: %v",
+                                        $1, NULL, 0, NULL);
+                       } else {
+                               $0 = new(binode);
+                               $0->op = Declare;
+                               $0->left = $<1;
+                               $0->right = NULL;
+                       }
                }$
 
 ###### print binode cases
 
        case Assign:
                do_indent(indent, "");
-               print_exec(b->left, indent, 0);
+               print_exec(b->left, indent, bracket);
                printf(" = ");
-               print_exec(b->right, indent, 0);
+               print_exec(b->right, indent, bracket);
                if (indent >= 0)
                        printf("\n");
                break;
 
        case Declare:
+               {
+               struct variable *v = cast(var, b->left)->var;
                do_indent(indent, "");
-               print_exec(b->left, indent, 0);
-               if (cast(var, b->left)->var->constant)
-                       printf(" ::= ");
-               else
-                       printf(" := ");
-               print_exec(b->right, indent, 0);
+               print_exec(b->left, indent, bracket);
+               if (cast(var, b->left)->var->constant) {
+                       if (v->where_decl == v->where_set) {
+                               printf("::");
+                               type_print(v->type, stdout);
+                               printf(" ");
+                       } else
+                               printf(" ::");
+               } else {
+                       if (v->where_decl == v->where_set) {
+                               printf(":");
+                               type_print(v->type, stdout);
+                               printf(" ");
+                       } else
+                               printf(" :");
+               }
+               if (b->right) {
+                       printf("= ");
+                       print_exec(b->right, indent, bracket);
+               }
                if (indent >= 0)
                        printf("\n");
+               }
                break;
 
 ###### propagate binode cases
 
        case Assign:
        case Declare:
-               /* Both must match and not be labels, result is Tnone */
-               t = propagate_types(b->left, c, ok, NULL, Rnolabel);
+               /* Both must match and not be labels,
+                * Type must support 'dup',
+                * For Assign, left must not be constant.
+                * result is Tnone
+                */
+               t = propagate_types(b->left, c, ok, NULL,
+                                   Rnolabel | (b->op == Assign ? Rnoconstant : 0));
+               if (!b->right)
+                       return Tnone;
+
                if (t) {
                        if (propagate_types(b->right, c, ok, t, 0) != t)
                                if (b->left->type == Xvar)
                                        type_err(c, "info: variable '%v' was set as %1 here.",
-                                                cast(var, b->left)->var->where_set, t, rules, &Tnone);
+                                                cast(var, b->left)->var->where_set, t, rules, NULL);
                } else {
                        t = propagate_types(b->right, c, ok, NULL, Rnolabel);
                        if (t)
-                               propagate_types(b->left, c, ok, t, 0);
+                               propagate_types(b->left, c, ok, t,
+                                               (b->op == Assign ? Rnoconstant : 0));
                }
-               return &Tnone;
+               if (t && t->dup == NULL)
+                       type_err(c, "error: cannot assign value of type %1", b, t, 0, NULL);
+               return Tnone;
 
                break;
 
 ###### interp binode cases
 
        case Assign:
+               lleft = linterp_exec(c, b->left, &ltype);
+               right = interp_exec(c, b->right, &rtype);
+               if (lleft) {
+                       free_value(ltype, lleft);
+                       dup_value(ltype, &right, lleft);
+                       ltype = NULL;
+               }
+               break;
+
        case Declare:
        {
                struct variable *v = cast(var, b->left)->var;
+               struct value *val;
                if (v->merged)
                        v = v->merged;
-               right = interp_exec(b->right);
-               free_value(v->val);
-               v->val = right;
-               right.type = NULL;
+               val = var_value(c, v);
+               free_value(v->type, val);
+               if (v->type->prepare_type)
+                       v->type->prepare_type(c, v->type, 0);
+               if (b->right) {
+                       right = interp_exec(c, b->right, &rtype);
+                       memcpy(val, &right, rtype->size);
+                       rtype = Tnone;
+               } else {
+                       val_init(v->type, val);
+               }
                break;
        }
 
@@ -2408,18 +3671,32 @@ function.
 ###### Binode types
        Use,
 
+###### expr precedence
+       $TERM use       
+
 ###### SimpleStatement Grammar
        | use Expression ${
                $0 = new_pos(binode, $1);
                $0->op = Use;
                $0->right = $<2;
+               if ($0->right->type == Xvar) {
+                       struct var *v = cast(var, $0->right);
+                       if (v->var->type == Tnone) {
+                               /* Convert this to a label */
+                               struct value *val;
+
+                               v->var->type = Tlabel;
+                               val = global_alloc(c, Tlabel, v->var, NULL);
+                               val->label = val;
+                       }
+               }
        }$
 
 ###### print binode cases
 
        case Use:
                do_indent(indent, "use ");
-               print_exec(b->right, -1, 0);
+               print_exec(b->right, -1, bracket);
                if (indent >= 0)
                        printf("\n");
                break;
@@ -2433,7 +3710,7 @@ function.
 ###### interp binode cases
 
        case Use:
-               rv = interp_exec(b->right);
+               rv = interp_exec(c, b->right, &rvtype);
                break;
 
 ### The Conditional Statement
@@ -2548,154 +3825,144 @@ 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
+       // 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
+
+       // Both ForPart and Whilepart open scopes, and CondSuffix only
        // closes one - so in the first branch here we have another to close.
-       CondStatement -> ForThen WhilePart CondSuffix ${
-                       $0 = $<3;
-                       $0->forpart = $1.forpart; $1.forpart = NULL;
-                       $0->thenpart = $1.thenpart; $1.thenpart = NULL;
-                       $0->condpart = $2.condpart; $2.condpart = NULL;
-                       $0->dopart = $2.dopart; $2.dopart = NULL;
-                       var_block_close(config2context(config), CloseSequential);
+       CondStatement -> ForPart OptNL ThenPart OptNL WhilePart CondSuffix ${
+                       $0 = $<CS;
+                       $0->forpart = $<FP;
+                       $0->thenpart = $<TP;
+                       $0->condpart = $WP.condpart; $WP.condpart = NULL;
+                       $0->dopart = $WP.dopart; $WP.dopart = NULL;
+                       var_block_close(c, CloseSequential);
+                       }$
+               | ForPart OptNL WhilePart CondSuffix ${
+                       $0 = $<CS;
+                       $0->forpart = $<FP;
+                       $0->condpart = $WP.condpart; $WP.condpart = NULL;
+                       $0->dopart = $WP.dopart; $WP.dopart = NULL;
+                       var_block_close(c, CloseSequential);
                        }$
                | WhilePart CondSuffix ${
-                       $0 = $<2;
-                       $0->condpart = $1.condpart; $1.condpart = NULL;
-                       $0->dopart = $1.dopart; $1.dopart = NULL;
+                       $0 = $<CS;
+                       $0->condpart = $WP.condpart; $WP.condpart = NULL;
+                       $0->dopart = $WP.dopart; $WP.dopart = NULL;
                        }$
-               | SwitchPart CondSuffix ${
-                       $0 = $<2;
-                       $0->condpart = $<1;
+               | SwitchPart OptNL CasePart CondSuffix ${
+                       $0 = $<CS;
+                       $0->condpart = $<SP;
+                       $CP->next = $0->casepart;
+                       $0->casepart = $<CP;
+                       }$
+               | SwitchPart : IN OptNL CasePart CondSuffix OUT Newlines ${
+                       $0 = $<CS;
+                       $0->condpart = $<SP;
+                       $CP->next = $0->casepart;
+                       $0->casepart = $<CP;
                        }$
                | IfPart IfSuffix ${
-                       $0 = $<2;
-                       $0->condpart = $1.condpart; $1.condpart = NULL;
-                       $0->thenpart = $1.thenpart; $1.thenpart = NULL;
+                       $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(config2context(config), CloseSequential);
+                       var_block_close(c, CloseSequential);
                        }$
 
        CondSuffix -> IfSuffix ${
                        $0 = $<1;
                        // This is where we close scope of the whole
                        // "for" or "while" statement
-                       var_block_close(config2context(config), CloseSequential);
-               }$
-               | CasePart CondSuffix ${
-                       $0 = $<2;
-                       $1->next = $0->casepart;
-                       $0->casepart = $<1;
+                       var_block_close(c, CloseSequential);
                }$
-
-       $*casepart
-       CasePart -> Newlines case Expression OpenScope Block ${
-                       $0 = calloc(1,sizeof(struct casepart));
-                       $0->value = $<3;
-                       $0->action = $<5;
-                       var_block_close(config2context(config), CloseParallel);
+               | Newlines CasePart CondSuffix ${
+                       $0 = $<CS;
+                       $CP->next = $0->casepart;
+                       $0->casepart = $<CP;
                }$
-               | case Expression OpenScope Block ${
-                       $0 = calloc(1,sizeof(struct casepart));
-                       $0->value = $<2;
-                       $0->action = $<4;
-                       var_block_close(config2context(config), CloseParallel);
+               | CasePart CondSuffix ${
+                       $0 = $<CS;
+                       $CP->next = $0->casepart;
+                       $0->casepart = $<CP;
                }$
 
-       $*cond_statement
        IfSuffix -> Newlines ${ $0 = new(cond_statement); }$
-               | Newlines else OpenScope Block ${
-                       $0 = new(cond_statement);
-                       $0->elsepart = $<4;
-                       var_block_close(config2context(config), CloseElse);
-               }$
-               | else OpenScope Block ${
-                       $0 = new(cond_statement);
-                       $0->elsepart = $<3;
-                       var_block_close(config2context(config), CloseElse);
-               }$
-               | Newlines else OpenScope CondStatement ${
+               | Newlines ElsePart ${ $0 = $<EP; }$
+               | ElsePart ${$0 = $<EP; }$
+
+       ElsePart -> else OpenBlock Newlines ${
                        $0 = new(cond_statement);
-                       $0->elsepart = $<4;
-                       var_block_close(config2context(config), CloseElse);
+                       $0->elsepart = $<OB;
+                       var_block_close(c, CloseElse);
                }$
                | else OpenScope CondStatement ${
                        $0 = new(cond_statement);
-                       $0->elsepart = $<3;
-                       var_block_close(config2context(config), CloseElse);
+                       $0->elsepart = $<CS;
+                       var_block_close(c, CloseElse);
                }$
 
+       $*casepart
+       CasePart -> case Expression OpenScope ColonBlock ${
+                       $0 = calloc(1,sizeof(struct casepart));
+                       $0->value = $<Ex;
+                       $0->action = $<Bl;
+                       var_block_close(c, CloseParallel);
+               }$
 
        $*exec
        // These scopes are closed in CondSuffix
-       ForPart -> for OpenScope SimpleStatements ${
-                       $0 = reorder_bilist($<3);
-               }$
-               |  for OpenScope Block ${
-                       $0 = $<3;
+       ForPart -> for OpenBlock ${
+                       $0 = $<Bl;
                }$
 
-       ThenPart -> then OpenScope SimpleStatements ${
-                       $0 = reorder_bilist($<3);
-                       var_block_close(config2context(config), CloseSequential);
-               }$
-               |  then OpenScope Block ${
-                       $0 = $<3;
-                       var_block_close(config2context(config), CloseSequential);
-               }$
-
-       ThenPartNL -> ThenPart OptNL ${
-                       $0 = $<1;
-               }$
-
-       // This scope is closed in CondSuffix
-       WhileHead -> while OpenScope Block ${
-               $0 = $<3;
+       ThenPart -> then OpenBlock ${
+                       $0 = $<OB;
+                       var_block_close(c, CloseSequential);
                }$
 
        $cond_statement
-       ForThen -> ForPart OptNL ThenPartNL ${
-                       $0.forpart = $<1;
-                       $0.thenpart = $<3;
-               }$
-               | ForPart OptNL ${
-                       $0.forpart = $<1;
-               }$
-
        // This scope is closed in CondSuffix
-       WhilePart -> while OpenScope Expression Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.dopart = $<4;
+       WhilePart -> while UseBlock OptNL do Block ${
+                       $0.condpart = $<UB;
+                       $0.dopart = $<Bl;
                }$
-               | WhileHead OptNL do Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<1;
-                       $0.dopart = $<4;
+               | while OpenScope Expression ColonBlock ${
+                       $0.condpart = $<Exp;
+                       $0.dopart = $<Bl;
                }$
 
-       IfPart -> if OpenScope Expression OpenScope Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.thenpart = $<5;
-                       var_block_close(config2context(config), CloseParallel);
+       IfPart -> if UseBlock OptNL then OpenBlock ClosePara ${
+                       $0.condpart = $<UB;
+                       $0.thenpart = $<Bl;
                }$
-               | if OpenScope Block OptNL then OpenScope Block ${
-                       $0.type = Xcond_statement;
-                       $0.condpart = $<3;
-                       $0.thenpart = $<7;
-                       var_block_close(config2context(config), CloseParallel);
+               | if OpenScope Expression OpenScope ColonBlock ClosePara ${
+                       $0.condpart = $<Ex;
+                       $0.thenpart = $<Bl;
+               }$
+               | if OpenScope Expression OpenScope OptNL then Block ClosePara ${
+                       $0.condpart = $<Ex;
+                       $0.thenpart = $<Bl;
                }$
 
        $*exec
        // This scope is closed in CondSuffix
        SwitchPart -> switch OpenScope Expression ${
-                       $0 = $<3;
+                       $0 = $<Ex;
                }$
-               | switch OpenScope Block ${
-                       $0 = $<3;
+               | switch UseBlock ${
+                       $0 = $<Bl;
                }$
 
 ###### print exec cases
@@ -2706,13 +3973,13 @@ 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");
@@ -2724,12 +3991,12 @@ defined.
                                if (bracket)
                                        do_indent(indent, "while {\n");
                                else
-                                       do_indent(indent, "while:\n");
+                                       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");
+                                       do_indent(indent, "do\n");
                                print_exec(cs->dopart, indent+1, bracket);
                                if (bracket)
                                        do_indent(indent, "}\n");
@@ -2794,7 +4061,7 @@ defined.
                        if (bracket)
                                printf(" {\n");
                        else
-                               printf(":\n");
+                               printf("\n");
                        print_exec(cs->elsepart, indent+1, bracket);
                        if (bracket)
                                do_indent(indent, "}\n");
@@ -2809,28 +4076,28 @@ defined.
                // thenpart must return Tnone if there is a dopart,
                // otherwise it is like elsepart.
                // condpart must:
-               //    be bool if there is not casepart
+               //    be bool if there is no casepart
                //    match casepart->values if there is a switchpart
                //    either be bool or match casepart->value if there
                //             is a whilepart
-               // elsepart, casepart->action must match there return type
-               // expected of this statement.
+               // elsepart and casepart->action must match the return type
+               //   expected of this statement.
                struct cond_statement *cs = cast(cond_statement, prog);
                struct casepart *cp;
 
-               t = propagate_types(cs->forpart, c, ok, &Tnone, 0);
-               if (!vtype_compat(&Tnone, t, 0))
+               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 (!vtype_compat(&Tnone, t, 0))
+               t = propagate_types(cs->dopart, c, ok, Tnone, 0);
+               if (!type_compat(Tnone, t, 0))
                        *ok = 0;
                if (cs->dopart) {
-                       t = propagate_types(cs->thenpart, c, ok, &Tnone, 0);
-                       if (!vtype_compat(&Tnone, t, 0))
+                       t = propagate_types(cs->thenpart, c, ok, Tnone, 0);
+                       if (!type_compat(Tnone, t, 0))
                                *ok = 0;
                }
                if (cs->casepart == NULL)
-                       propagate_types(cs->condpart, c, ok, &Tbool, 0);
+                       propagate_types(cs->condpart, c, ok, Tbool, 0);
                else {
                        /* Condpart must match case values, with bool permitted */
                        t = NULL;
@@ -2870,231 +4137,412 @@ defined.
        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);
+               if (cs->forpart)
+                       interp_exec(c, cs->forpart, NULL);
                do {
-                       if (c->condpart)
-                               cnd = interp_exec(c->condpart);
+                       if (cs->condpart)
+                               cnd = interp_exec(c, cs->condpart, &cndtype);
                        else
-                               cnd.type = &Tnone;
-                       if (!(cnd.type == &Tnone ||
-                             (cnd.type == &Tbool && cnd.bool != 0)))
+                               cndtype = Tnone;
+                       if (!(cndtype == Tnone ||
+                             (cndtype == Tbool && cnd.bool != 0)))
                                break;
                        // cnd is Tnone or Tbool, doesn't need to be freed
-                       if (c->dopart)
-                               interp_exec(c->dopart);
-
-                       if (c->thenpart) {
-                               v = interp_exec(c->thenpart);
-                               if (v.type != &Tnone || !c->dopart)
-                                       return v;
-                               free_value(v);
+                       if (cs->dopart)
+                               interp_exec(c, cs->dopart, NULL);
+
+                       if (cs->thenpart) {
+                               rv = interp_exec(c, cs->thenpart, &rvtype);
+                               if (rvtype != Tnone || !cs->dopart)
+                                       goto Xcond_done;
+                               free_value(rvtype, &rv);
+                               rvtype = Tnone;
                        }
-               } while (c->dopart);
-
-               for (cp = c->casepart; cp; cp = cp->next) {
-                       v = interp_exec(cp->value);
-                       if (value_cmp(v, cnd) == 0) {
-                               free_value(v);
-                               free_value(cnd);
-                               return interp_exec(cp->action);
+               } while (cs->dopart);
+
+               for (cp = cs->casepart; cp; cp = cp->next) {
+                       v = interp_exec(c, cp->value, &vtype);
+                       if (value_cmp(cndtype, vtype, &v, &cnd) == 0) {
+                               free_value(vtype, &v);
+                               free_value(cndtype, &cnd);
+                               rv = interp_exec(c, cp->action, &rvtype);
+                               goto Xcond_done;
                        }
-                       free_value(v);
+                       free_value(vtype, &v);
                }
-               free_value(cnd);
-               if (c->elsepart)
-                       return interp_exec(c->elsepart);
-               v.type = &Tnone;
-               return v;
+               free_value(cndtype, &cnd);
+               if (cs->elsepart)
+                       rv = interp_exec(c, cs->elsepart, &rvtype);
+               else
+                       rvtype = Tnone;
+       Xcond_done:
+               break;
        }
 
-### Finally the whole program.
+### Top level structure
 
-Somewhat reminiscent of Pascal a (current) Ocean program starts with
-the keyword "program" and a list of variable names which are assigned
-values from command line arguments.  Following this is a `block` which
-is the code to execute.
+All the language elements so far can be used in various places.  Now
+it is time to clarify what those places are.
 
-As this is the top level, several things are handled a bit
-differently.
-The whole program is not interpreted by `interp_exec` as that isn't
-passed the argument list which the program requires.  Similarly type
-analysis is a bit more interesting at this level.
+At the top level of a file there will be a number of declarations.
+Many of the things that can be declared haven't been described yet,
+such as functions, procedures, imports, and probably more.
+For now there are two sorts of things that can appear at the top
+level.  They are predefined constants, `struct` types, and the `main`
+function.  While the syntax will allow the `main` function to appear
+multiple times, that will trigger an error if it is actually attempted.
 
-###### Binode types
-       Program,
+The various declarations do not return anything.  They store the
+various declarations in the parse context.
 
 ###### Parser: grammar
 
-       $*binode
-       Program -> program OpenScope Varlist Block OptNL ${
-               $0 = new(binode);
-               $0->op = Program;
-               $0->left = reorder_bilist($<3);
-               $0->right = $<4;
-               var_block_close(config2context(config), CloseSequential);
-               if (config2context(config)->scope_stack) abort();
-               }$
-               | ERROR ${
-                       tok_err(config2context(config),
-                               "error: unhandled parse error.", &$1);
-               }$
+       $void
+       Ocean -> OptNL DeclarationList
 
-       Varlist -> Varlist ArgDecl ${
-                       $0 = new(binode);
-                       $0->op = Program;
-                       $0->left = $<1;
-                       $0->right = $<2;
+       ## declare terminals
+
+       OptNL ->
+               | OptNL NEWLINE
+       Newlines -> NEWLINE
+               | Newlines NEWLINE
+
+       DeclarationList -> Declaration
+               | DeclarationList Declaration
+
+       Declaration -> ERROR Newlines ${
+                       tok_err(c,
+                               "error: unhandled parse error", &$1);
                }$
-               | ${ $0 = NULL; }$
+               | DeclareConstant
+               | DeclareFunction
+               | DeclareStruct
 
-       $*var
-       ArgDecl -> IDENTIFIER ${ {
-               struct variable *v = var_decl(config2context(config), $1.txt);
-               $0 = new(var);
-               $0->var = v;
-       } }$
+       ## top level grammar
 
        ## Grammar
 
+### The `const` section
+
+As well as being defined in with the code that uses them, constants
+can be declared at the top level.  These have full-file scope, so they
+are always `InScope`.  The value of a top level constant can be given
+as an expression, and this is evaluated immediately rather than in the
+later interpretation stage.  Once we add functions to the language, we
+will need rules concern which, if any, can be used to define a top
+level constant.
+
+Constants are defined in a section that starts with the reserved word
+`const` and then has a block with a list of assignment statements.
+For syntactic consistency, these must use the double-colon syntax to
+make it clear that they are constants.  Type can also be given: if
+not, the type will be determined during analysis, as with other
+constants.
+
+As the types constants are inserted at the head of a list, printing
+them in the same order that they were read is not straight forward.
+We take a quadratic approach here and count the number of constants
+(variables of depth 0), then count down from there, each time
+searching through for the Nth constant for decreasing N.
+
+###### top level grammar
+
+       $TERM const
+
+       DeclareConstant -> const { IN OptNL ConstList OUT OptNL } Newlines
+               | const { SimpleConstList } Newlines
+               | const IN OptNL ConstList OUT Newlines
+               | const SimpleConstList Newlines
+
+       ConstList -> ConstList SimpleConstLine
+               | SimpleConstLine
+       SimpleConstList -> SimpleConstList ; Const
+               | Const
+               | SimpleConstList ;
+       SimpleConstLine -> SimpleConstList Newlines
+               | ERROR Newlines ${ tok_err(c, "Syntax error in constant", &$1); }$
+
+       $*type
+       CType -> Type   ${ $0 = $<1; }$
+               |       ${ $0 = NULL; }$
+       $void
+       Const -> IDENTIFIER :: CType = Expression ${ {
+               int ok;
+               struct variable *v;
+
+               v = var_decl(c, $1.txt);
+               if (v) {
+                       struct var *var = new_pos(var, $1);
+                       v->where_decl = var;
+                       v->where_set = var;
+                       var->var = v;
+                       v->constant = 1;
+               } else {
+                       v = 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);
+               }
+               do {
+                       ok = 1;
+                       propagate_types($5, c, &ok, $3, 0);
+               } while (ok == 2);
+               if (!ok)
+                       c->parse_error = 1;
+               else if (v) {
+                       struct value res = interp_exec(c, $5, &v->type);
+                       global_alloc(c, v->type, v, &res);
+               }
+       } }$
+
+###### print const decls
+       {
+               struct variable *v;
+               int target = -1;
+
+               while (target != 0) {
+                       int i = 0;
+                       for (v = context.in_scope; v; v=v->in_scope)
+                               if (v->depth == 0) {
+                                       i += 1;
+                                       if (i == target)
+                                               break;
+                               }
+
+                       if (target == -1) {
+                               if (i)
+                                       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->type, stdout);
+                               printf(" = ");
+                               if (v->type == Tstr)
+                                       printf("\"");
+                               print_value(v->type, val);
+                               if (v->type == Tstr)
+                                       printf("\"");
+                               printf("\n");
+                               target -= 1;
+                       }
+               }
+       }
+
+### Finally the whole `main` function.
+
+An Ocean program can currently have only one function - `main` - and
+that must exist.  It expects an array of strings with a provided size.
+Following this is a `block` which is the code to execute.
+
+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.
+
+###### top level grammar
+
+       DeclareFunction -> MainFunction ${ {
+               if (c->prog)
+                       type_err(c, "\"main\" defined a second time",
+                                $1, NULL, 0, NULL);
+               else
+                       c->prog = $<1;
+       } }$
+
 ###### print binode cases
-       case Program:
-               do_indent(indent, "program");
+       case Func:
+       case List:
+               do_indent(indent, "func main(");
                for (b2 = cast(binode, b->left); b2; b2 = cast(binode, b2->right)) {
+                       struct variable *v = cast(var, b2->left)->var;
                        printf(" ");
                        print_exec(b2->left, 0, 0);
+                       printf(":");
+                       type_print(v->type, stdout);
                }
                if (bracket)
-                       printf(" {\n");
+                       printf(") {\n");
                else
-                       printf(":\n");
+                       printf(")\n");
                print_exec(b->right, indent+1, bracket);
                if (bracket)
                        do_indent(indent, "}\n");
                break;
 
 ###### propagate binode cases
-       case Program: abort();
+       case List:
+       case Func: abort();             // NOTEST
 
 ###### core functions
 
        static int analyse_prog(struct exec *prog, struct parse_context *c)
        {
-               struct binode *b = cast(binode, prog);
+               struct binode *bp = cast(binode, prog);
+               struct binode *b;
                int ok = 1;
+               int arg = 0;
+               struct type *argv_type;
+               struct text argv_type_name = { " argv", 5 };
 
-               if (!b)
-                       return 0;
-               do {
-                       ok = 1;
-                       propagate_types(b->right, c, &ok, &Tnone, 0);
-               } while (ok == 2);
-               if (!ok)
-                       return 0;
+               if (!bp)
+                       return 0;       // NOTEST
 
-               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_init(&Tstr);
+               argv_type = add_type(c, argv_type_name, &array_prototype);
+               argv_type->array.member = Tstr;
+               argv_type->array.unspec = 1;
+
+               for (b = cast(binode, bp->left); 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
                        }
                }
-               b = cast(binode, prog);
+
                do {
                        ok = 1;
-                       propagate_types(b->right, c, &ok, &Tnone, 0);
+                       propagate_types(bp->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;
+               propagate_types(bp->right, c, &ok, Tnone, 0);
+               if (!ok)
+                       return 0;
+               scope_finalize(c);
+               return 1;
        }
 
-       static void interp_prog(struct exec *prog, char **argv)
+       static void interp_prog(struct parse_context *c, struct exec *prog, 
+                               int argc, char **argv)
        {
                struct binode *p = cast(binode, prog);
                struct binode *al;
+               int anum = 0;
                struct value v;
+               struct type *vtype;
 
                if (!prog)
-                       return;
+                       return;         // NOTEST
                al = cast(binode, p->left);
                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, p->right, &vtype);
+               free_value(vtype, &v);
        }
 
 ###### interp binode cases
-       case Program: abort();
+       case List:
+       case Func: abort();     // NOTEST
 
 ## And now to test it out.
 
-Having a language requires having a "hello world" program. I'll
+Having a language requires having a "hello world" program.  I'll
 provide a little more than that: a program that prints "Hello world"
 finds the GCD of two numbers, prints the first few elements of
-Fibonacci, and performs a binary search for a number.
+Fibonacci, performs a binary search for a number, and a few other
+things which will likely grow as the languages grows.
 
 ###### File: oceani.mk
-       tests :: sayhello
+       demos :: sayhello
        sayhello : oceani
-               @echo "===== TEST ====="
-               ./oceani --section "test: hello" oceani.mdc 55 33
+               @echo "===== DEMO ====="
+               ./oceani --section "demo: hello" oceani.mdc 55 33
+
+###### demo: hello
+
+       const
+               pi ::= 3.141_592_6
+               four ::= 2 + 2 ; five ::= 10/2
+       const pie ::= "I like Pie";
+               cake ::= "The cake is"
+                 ++ " a lie"
+
+       struct fred
+               size:[four]number
+               name:string
+               alive:Boolean
+
+       func main
+               argv:[argc::]string
+       do
+               print "Hello World, what lovely oceans you have!"
+               print "Are there", five, "?"
+               print pi, pie, "but", cake
 
-###### test: hello
+               A := $argv[1]; B := $argv[2]
 
-       program A B:
-               print "Hello World, what lovely oceans you have!"
                /* When a variable is defined in both branches of an 'if',
                 * and used afterwards, the variables are merged.
                 */
                if A > B:
                        bigger := "yes"
-               else:
+               else
                        bigger := "no"
                print "Is", A, "bigger than", B,"? ", bigger
                /* If a variable is not used after the 'if', no
                 * merge happens, so types can be different
                 */
-               if A * 2 > B:
-                       double := "yes"
+               if A > B * 2:
+                       double:string = "yes"
                        print A, "is more than twice", B, "?", double
-               else:
-                       double := A*2
-                       print "double", A, "is only", double
+               else
+                       double := B*2
+                       print "double", B, "is", double
 
-               a := A; b := B
-               if a > 0 and b > 0:
+               a : number
+               a = A;
+               b:number = B
+               if a > 0 and then b > 0:
                        while a != b:
                                if a < b:
                                        b = b - a
-                               else:
+                               else
                                        a = a - b
                        print "GCD of", A, "and", B,"is", a
                else if a <= 0:
                        print a, "is not positive, cannot calculate GCD"
-               else:
+               else
                        print b, "is not positive, cannot calculate GCD"
 
-               for:
+               for
                        togo := 10
                        f1 := 1; f2 := 1
                        print "Fibonacci:", f1,f2,
@@ -3107,22 +4555,54 @@ Fibonacci, and performs a binary search for a number.
                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:
                                use GiveUp
                        use True
-               do: pass
+               do pass
                case Found:
                        print "Yay, I found", target
                case GiveUp:
                        print "Closest I found was", mid
+
+               size::= 10
+               list:[size]number
+               list[0] = 1234
+               // "middle square" PRNG.  Not particularly good, but one my
+               // Dad taught me - the first one I ever heard of.
+               for i:=1; then i = i + 1; while i < size:
+                       n := list[i-1] * list[i-1]
+                       list[i] = (n / 100) % 10 000
+
+               print "Before sort:",
+               for i:=0; then i = i + 1; while i < size:
+                       print "", list[i],
+               print
+
+               for i := 1; then i=i+1; while i < size:
+                       for j:=i-1; then j=j-1; while j >= 0:
+                               if list[j] > list[j+1]:
+                                       t:= list[j]
+                                       list[j] = list[j+1]
+                                       list[j+1] = t
+               print " After sort:",
+               for i:=0; then i = i + 1; while i < size:
+                       print "", list[i],
+               print
+
+               if 1 == 2 then print "yes"; else print "no"
+
+               bob:fred
+               bob.name = "Hello"
+               bob.alive = (bob.name == "Hello")
+               print "bob", "is" if  bob.alive else "isn't", "alive"