]> ocean-lang.org Git - ocean/blobdiff - csrc/oceani.mdc
Oceani - Jamison Creek Version
[ocean] / csrc / oceani.mdc
index 123fffc46ccad91e5b8849de22a8a0adaa3177d7..ce70c49e2b6231240905d843456be054ef10ab3d 100644 (file)
@@ -1,4 +1,4 @@
-# Ocean Interpreter - Stoney Creek version
+# Ocean Interpreter - Jamison Creek version
 
 Ocean is intended to be a compiled language, so this interpreter is
 not targeted at being the final product.  It is, rather, an intermediate
 
 Ocean is intended to be a compiled language, so this interpreter is
 not targeted at being the final product.  It is, rather, an intermediate
@@ -29,35 +29,31 @@ be.
 
 ## Current version
 
 
 ## 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 "=".
+Elements that are present purely to make a usable language, and
+without any expectation that they will remain, are the "program'
+clause, which provides a list of variables to received command-line
+arguments, and the "print" statement which performs simple output.
 
 
-Elements which are present to make a usable language are:
-
- - "blocks" of multiple statements.
- - `pass`: a statement which does nothing.
- - expressions: `+`, `-`, `*`, `/`, `%` can apply to numbers and `++` can
-   catenate strings.  `and`, `or`, `not` manipulate Booleans, and
-   normal comparison operators can work on all three types.
- - `print`: will print the values in a list of expressions.
- - `program`: is given a list of identifiers to initialize from
-   arguments.
+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
 
 ## 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
 Creek.
 
 Once we have something reasonably resembling a complete language, the
@@ -76,7 +72,7 @@ So the main requirements of the interpreter are:
 - Parse the program, possibly with tracing,
 - Analyse the parsed program to ensure consistency,
 - Print the program,
 - Parse the program, possibly with tracing,
 - Analyse the parsed program to ensure consistency,
 - Print the program,
-- Execute the program.
+- Execute the program, if no parsing or consistency errors were found.
 
 This is all performed by a single C program extracted with
 `parsergen`.
 
 This is all performed by a single C program extracted with
 `parsergen`.
@@ -114,6 +110,7 @@ structures can be used.
                struct token_config config;
                char *file_name;
                int parse_error;
                struct token_config config;
                char *file_name;
                int parse_error;
+               struct exec *prog;
                ## parse context
        };
 
                ## parse context
        };
 
@@ -167,7 +164,7 @@ structures can be used.
                int fd;
                int len;
                char *file;
                int fd;
                int len;
                char *file;
-               struct section *s;
+               struct section *s, *ss;
                char *section = NULL;
                struct parse_context context = {
                        .config = {
                char *section = NULL;
                struct parse_context context = {
                        .config = {
@@ -179,7 +176,6 @@ structures can be used.
                        },
                };
                int doprint=0, dotrace=0, doexec=1, brackets=0;
                        },
                };
                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) {
                int opt;
                while ((opt = getopt_long(argc, argv, options, long_options, NULL))
                       != -1) {
@@ -215,41 +211,39 @@ structures can be used.
                ## context initialization
 
                if (section) {
                ## 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;
                        }
                        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
                                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;
+               parse_oceani(ss->code, &context.config, dotrace ? stderr : NULL);
+
+               if (!context.prog) {
+                       fprintf(stderr, "oceani: no program found.\n");
                        context.parse_error = 1;
                }
                        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);
                        }
                                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.prog, argv+optind+1);
                }
                }
+               free_exec(context.prog);
+
                while (s) {
                        struct section *t = s->next;
                        code_free(s->code);
                while (s) {
                        struct section *t = s->next;
                        code_free(s->code);
@@ -292,12 +286,12 @@ 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
 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'.
+processing command line arguments.  These can be assigned to any of
+several types of variable, but we must first know the correct type so
+any required conversion can happen.  If a variable is associated with
+a command line argument but no type can be interpreted (e.g. the
+variable is only ever used in a `print` statement), then the type is
+set to 'string'.
 
 Undeclared names may only appear in "use" statements and "case" expressions.
 These names are given a type of "label" and a unique value.
 
 Undeclared names may only appear in "use" statements and "case" expressions.
 These names are given a type of "label" and a unique value.
@@ -305,9 +299,9 @@ 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
 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
 
 As there are, as yet, no distinct types that are compatible, there
 isn't much subtlety in the analysis.  When we have distinct number
@@ -317,8 +311,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,
 
 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
 
 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
@@ -375,8 +370,8 @@ context so indicate that parsing failed.
                        }
                        fmt++;
                        switch (*fmt) {
                        }
                        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':
                                type_print(t1, stderr);
                                break;
                        case '1':
                                type_print(t1, stderr);
                                break;
@@ -397,17 +392,15 @@ context so indicate that parsing failed.
                c->parse_error = 1;
        }
 
                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.  These will introduced and described
+here.  The following section will present the different specific code
+elements which comprise or manipulate these various entities.
 
 ### Types
 
 
 ### Types
 
@@ -421,8 +414,9 @@ 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
 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 eventaully enable types to
+determine if they are compatible with other types, though such types
+have not yet been implemented.
 
 Named type are stored in a simple linked list.  Objects of each type are "values"
 which are often passed around by value.
 
 Named type are stored in a simple linked list.  Objects of each type are "values"
 which are often passed around by value.
@@ -448,10 +442,12 @@ which are often passed around by value.
                int (*cmp_eq)(struct value v1, struct value v2);
                struct value (*dup)(struct value val);
                void (*free)(struct value val);
                int (*cmp_eq)(struct value v1, struct value v2);
                struct value (*dup)(struct value val);
                void (*free)(struct value val);
+               void (*free_type)(struct type *t);
                int (*compat)(struct type *this, struct type *other);
                long long (*to_int)(struct value *v);
                double (*to_float)(struct value *v);
                int (*to_mpq)(mpq_t *q, struct value *v);
                int (*compat)(struct type *this, struct type *other);
                long long (*to_int)(struct value *v);
                double (*to_float)(struct value *v);
                int (*to_mpq)(mpq_t *q, struct value *v);
+               ## type functions
                union {
                        ## type union fields
                };
                union {
                        ## type union fields
                };
@@ -523,7 +519,7 @@ which are often passed around by value.
                else if (type->print_type)
                        type->print_type(type, f);
                else
                else if (type->print_type)
                        type->print_type(type, f);
                else
-                       fputs("*invalid*type*", f);
+                       fputs("*invalid*type*", f);     // NOTEST
        }
 
        static struct value val_prepare(struct type *type)
        }
 
        static struct value val_prepare(struct type *type)
@@ -567,7 +563,7 @@ which are often passed around by value.
                if (v.type && v.type->print)
                        v.type->print(v);
                else
                if (v.type && v.type->print)
                        v.type->print(v);
                else
-                       printf("*Unknown*");
+                       printf("*Unknown*");            // NOTEST
        }
 
        static struct value parse_value(struct type *type, char *arg)
        }
 
        static struct value parse_value(struct type *type, char *arg)
@@ -576,8 +572,8 @@ which are often passed around by value.
 
                if (type && type->parse)
                        return type->parse(type, arg);
 
                if (type && type->parse)
                        return type->parse(type, arg);
-               rv.type = NULL;
-               return rv;
+               rv.type = NULL;                         // NOTEST
+               return rv;                              // NOTEST
        }
 
 ###### forward decls
        }
 
 ###### forward decls
@@ -597,6 +593,8 @@ which are often passed around by value.
                struct type *t = context.typelist;
 
                context.typelist = t->next;
                struct type *t = context.typelist;
 
                context.typelist = t->next;
+               if (t->free_type)
+                       t->free_type(t);
                free(t);
        }
 
                free(t);
        }
 
@@ -617,11 +615,18 @@ 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`).
 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.
 
 When assigning command line arguments to variables, we need to be able
 to parse each type from a string.
 
 
 When assigning command line arguments to variables, we need to be able
 to parse each type from a string.
 
+The distinction beteen "prepare" and "init" needs to be explained.
+"init" sets up an initial value, such as "zero" or the empty string.
+"prepare" simply prepares the data structure so that if "free" gets
+called on it, it won't do something silly.  Normally a value will be
+stored after "prepare" but before "free", but this might not happen if
+there are errors.
+
 ###### includes
        #include <gmp.h>
        #include "string.h"
 ###### includes
        #include <gmp.h>
        #include "string.h"
@@ -685,8 +690,8 @@ to parse each type from a string.
 
                rv.type = type;
                switch(type->vtype) {
 
                rv.type = type;
                switch(type->vtype) {
-               case Vnone:
-                       break;
+               case Vnone:             // NOTEST
+                       break;          // NOTEST
                case Vnum:
                        mpq_init(rv.num); break;
                case Vstr:
                case Vnum:
                        mpq_init(rv.num); break;
                case Vstr:
@@ -696,9 +701,9 @@ to parse each type from a string.
                case Vbool:
                        rv.bool = 0;
                        break;
                case Vbool:
                        rv.bool = 0;
                        break;
-               case Vlabel:
-                       rv.label = NULL;
-                       break;
+               case Vlabel:                    // NOTEST
+                       rv.label = NULL;        // NOTEST
+                       break;                  // NOTEST
                }
                return rv;
        }
                }
                return rv;
        }
@@ -708,8 +713,8 @@ to parse each type from a string.
                struct value rv;
                rv.type = v.type;
                switch (rv.type->vtype) {
                struct value rv;
                rv.type = v.type;
                switch (rv.type->vtype) {
-               case Vnone:
-                       break;
+               case Vnone:             // NOTEST
+                       break;          // NOTEST
                case Vlabel:
                        rv.label = v.label;
                        break;
                case Vlabel:
                        rv.label = v.label;
                        break;
@@ -733,13 +738,13 @@ to parse each type from a string.
        {
                int cmp;
                if (left.type != right.type)
        {
                int cmp;
                if (left.type != right.type)
-                       return left.type - right.type;
+                       return left.type - right.type;  // NOTEST
                switch (left.type->vtype) {
                case Vlabel: cmp = left.label == right.label ? 0 : 1; break;
                case Vnum: cmp = mpq_cmp(left.num, right.num); break;
                case Vstr: cmp = text_cmp(left.str, right.str); break;
                case Vbool: cmp = left.bool - right.bool; break;
                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;
+               case Vnone: cmp = 0;                    // NOTEST
                }
                return cmp;
        }
                }
                return cmp;
        }
@@ -747,10 +752,10 @@ to parse each type from a string.
        static void _print_value(struct value v)
        {
                switch (v.type->vtype) {
        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;
+               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;
                case Vbool:
                case Vstr:
                        printf("%.*s", v.str.len, v.str.txt); break;
                case Vbool:
@@ -776,10 +781,10 @@ to parse each type from a string.
 
                val.type = type;
                switch(type->vtype) {
 
                val.type = type;
                switch(type->vtype) {
-               case Vlabel:
-               case Vnone:
-                       val.type = NULL;
-                       break;
+               case Vlabel:                            // NOTEST
+               case Vnone:                             // NOTEST
+                       val.type = NULL;                // NOTEST
+                       break;                          // NOTEST
                case Vstr:
                        val.str.len = strlen(arg);
                        val.str.txt = malloc(val.str.len);
                case Vstr:
                        val.str.len = strlen(arg);
                        val.str.txt = malloc(val.str.len);
@@ -998,7 +1003,6 @@ like "if" and the code following it.
        $void
        OpenScope -> ${ scope_push(config2context(config)); }$
 
        $void
        OpenScope -> ${ scope_push(config2context(config)); }$
 
-
 Each variable records a scope depth and is in one of four states:
 
 - "in scope".  This is the case between the declaration of the
 Each variable records a scope depth and is in one of four states:
 
 - "in scope".  This is the case between the declaration of the
@@ -1048,6 +1052,13 @@ 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.
 
 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.
+
 ###### variable fields
        struct variable *merged;
 
 ###### variable fields
        struct variable *merged;
 
@@ -1082,6 +1093,9 @@ no longer be primary.
 
                        v = t->previous;
                        free_value(t->val);
 
                        v = t->previous;
                        free_value(t->val);
+                       if (t->min_depth == 0)
+                               // This is a global constant
+                               free_exec(t->where_decl);
                        free(t);
                }
        }
                        free(t);
                }
        }
@@ -1096,21 +1110,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.
 
 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
 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
+assignment) 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
 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
 
 When exiting a parallel scope we check if there are any variables that
 were previously pending and are still visible. If there are, then
@@ -1169,7 +1185,7 @@ all pending-scope variables become conditionally scoped.
                switch (v ? v->scope : OutScope) {
                case OutScope:
                case PendingScope:
                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
                        return NULL;
                case CondScope:
                        /* All CondScope variables of this name need to be merged
@@ -1190,14 +1206,14 @@ all pending-scope variables become conditionally scoped.
 
        static void var_block_close(struct parse_context *c, enum closetype ct)
        {
 
        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;
                     ) {
                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) {
                        case CloseElse:
                        case CloseParallel: /* handle PendingScope */
                                switch(v->scope) {
@@ -1258,7 +1274,7 @@ all pending-scope variables become conditionally scoped.
                                }
                                break;
                        }
                                }
                                break;
                        }
-                       if (v->scope == OutScope)
+                       if (v->scope == OutScope || v->name->var != v)
                                *vp = v->in_scope;
                        else
                                vp = &v->in_scope;
                                *vp = v->in_scope;
                        else
                                vp = &v->in_scope;
@@ -1317,7 +1333,7 @@ subclasses, and to access these we need to be able to `cast` the
        static int __fput_loc(struct exec *loc, FILE *f)
        {
                if (!loc)
        static int __fput_loc(struct exec *loc, FILE *f)
        {
                if (!loc)
-                       return 0;
+                       return 0;               // NOTEST
                if (loc->line >= 0) {
                        fprintf(f, "%d:%d: ", loc->line, loc->column);
                        return 1;
                if (loc->line >= 0) {
                        fprintf(f, "%d:%d: ", loc->line, loc->column);
                        return 1;
@@ -1330,7 +1346,7 @@ subclasses, and to access these we need to be able to `cast` the
        static void fput_loc(struct exec *loc, FILE *f)
        {
                if (!__fput_loc(loc, f))
        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
        }
 
 Each different type of `exec` node needs a number of functions
@@ -1342,9 +1358,9 @@ slowly.
 #### Freeing
 
 The parser generator requires a `free_foo` function for each struct
 #### 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
 
 
 ###### ast functions
 
@@ -1401,7 +1417,7 @@ also want to know what sort of bracketing to use.
        static void print_exec(struct exec *e, int indent, int bracket)
        {
                if (!e)
        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;
                switch (e->type) {
                case Xbinode:
                        print_binode(cast(binode, e), indent, bracket); break;
@@ -1468,7 +1484,13 @@ within the `exec` tree.  The exception to this is the whole `program`
 which needs to look at command line arguments.  The `program` will be
 interpreted separately.
 
 which needs to look at command line arguments.  The `program` 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, which may be `Tnone` but must be
+non-NULL;  Some `exec`s will return the location of a value, which can
+be updates.  To support this, each exec case must store either a value
+in `val` or the pointer to a value in `lval`.  If `lval` is set, but a
+simple value is required, `inter_exec()` will dereference `lval` to
+get the value.
+
 
 ###### core functions
 
 
 ###### core functions
 
@@ -1525,9 +1547,566 @@ Each `exec` can return a value, which may be `Tnone` but must be non-NULL;
                return ret;
        }
 
                return ret;
        }
 
-## Language elements
+### Complex types
+
+Now that we have the shape of the interpreter in place we can add some
+complex types and connected them in to the data structures and the
+different phases of parse, analyse, print, interpret.
+
+Thus far we have arrays and structs.
+
+#### 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 an arbitrary expression which is evaluated when the name
+comes into scope.
+
+Arrays cannot be assigned.  When pointers are introduced we will also
+introduce array slices which can refer to part or all of an array -
+the assignment syntax will create a slice.  For now, an array can only
+ever be referenced by the name it is declared with.  It is likely that
+a "`copy`" primitive will eventually be define which can be used to
+make a copy of an array with controllable depth.
+
+###### type union fields
+
+       struct {
+               int size;
+               struct variable *vsize;
+               struct type *member;
+       } array;
+
+###### value union fields
+       struct {
+               struct value *elmnts;
+       } array;
+
+###### value functions
+
+       static struct value array_prepare(struct type *type)
+       {
+               struct value ret;
+
+               ret.type = type;
+               ret.array.elmnts = NULL;
+               return ret;
+       }
+
+       static struct value array_init(struct type *type)
+       {
+               struct value ret;
+               int i;
+
+               ret.type = type;
+               if (type->array.vsize) {
+                       mpz_t q;
+                       mpz_init(q);
+                       mpz_tdiv_q(q, mpq_numref(type->array.vsize->val.num),
+                                  mpq_denref(type->array.vsize->val.num));
+                       type->array.size = mpz_get_si(q);
+                       mpz_clear(q);
+               }
+               ret.array.elmnts = calloc(type->array.size,
+                                         sizeof(ret.array.elmnts[0]));
+               for (i = 0; ret.array.elmnts && i < type->array.size; i++)
+                       ret.array.elmnts[i] = val_init(type->array.member);
+               return ret;
+       }
+
+       static void array_free(struct value val)
+       {
+               int i;
+
+               if (val.array.elmnts)
+                       for (i = 0; i < val.type->array.size; i++)
+                               free_value(val.array.elmnts[i]);
+               free(val.array.elmnts);
+       }
+
+       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 (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]", b->name.len, b->name.txt);
+               } else
+                       fprintf(f, "%d]", type->array.size);
+               type_print(type->array.member, f);
+       }
+
+       static struct type array_prototype = {
+               .prepare = array_prepare,
+               .init = array_init,
+               .print_type = array_print_type,
+               .compat = array_compat,
+               .free = array_free,
+       };
+
+###### type grammar
+
+       | [ NUMBER ] Type ${
+               $0 = calloc(1, sizeof(struct type));
+               *($0) = array_prototype;
+               $0->array.member = $<4;
+               $0->array.vsize = NULL;
+               {
+               struct parse_context *c = config2context(config);
+               char tail[3];
+               mpq_t num;
+               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 {
+                       $0->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);
+               }
+               $0->next= c->anon_typelist;
+               c->anon_typelist = $0;
+               }
+       }$
+
+       | [ IDENTIFIER ] Type ${ {
+               struct parse_context *c = config2context(config);
+               struct variable *v = var_ref(c, $2.txt);
+
+               if (!v)
+                       tok_err(config2context(config), "error: name undeclared", &$2);
+               else if (!v->constant)
+                       tok_err(config2context(config), "error: array size must be a constant", &$2);
+
+               $0 = calloc(1, sizeof(struct type));
+               *($0) = array_prototype;
+               $0->array.member = $<4;
+               $0->array.size = 0;
+               $0->array.vsize = v;
+               $0->next= c->anon_typelist;
+               c->anon_typelist = $0;
+       } }$
+
+###### parse context
+
+       struct type *anon_typelist;
+
+###### free context types
+
+       while (context.anon_typelist) {
+               struct type *t = context.anon_typelist;
+
+               context.anon_typelist = t->next;
+               free(t);
+       }
+
+###### 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, 0);
+               printf("[");
+               print_exec(b->right, -1, 0);
+               printf("]");
+               break;
 
 
-Each language element needs to be parsed, printed, analysed,
+###### 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);
+                       *ok = 0;
+                       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);
+                               *ok = 0;
+                       }
+                       return t->array.member;
+               }
+               break;
+
+###### interp binode cases
+       case Index: {
+               mpz_t q;
+               long i;
+
+               lleft = linterp_exec(b->left);
+               right = interp_exec(b->right);
+               mpz_init(q);
+               mpz_tdiv_q(q, mpq_numref(right.num), mpq_denref(right.num));
+               i = mpz_get_si(q);
+               mpz_clear(q);
+
+               if (i >= 0 && i < lleft->type->array.size)
+                       lrv = &lleft->array.elmnts[i];
+               else
+                       rv = val_init(lleft->type->array.member);
+               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 initializes 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;
+               } *fields;
+       } structure;
+
+###### value union fields
+       struct {
+               struct value *fields;
+       } structure;
+
+###### type functions
+       void (*print_type_decl)(struct type *type, FILE *f);
+
+###### value functions
+
+       static struct value structure_prepare(struct type *type)
+       {
+               struct value ret;
+
+               ret.type = type;
+               ret.structure.fields = NULL;
+               return ret;
+       }
+
+       static struct value structure_init(struct type *type)
+       {
+               struct value ret;
+               int i;
+
+               ret.type = type;
+               ret.structure.fields = calloc(type->structure.nfields,
+                                             sizeof(ret.structure.fields[0]));
+               for (i = 0; ret.structure.fields && i < type->structure.nfields; i++)
+                       ret.structure.fields[i] = val_init(type->structure.fields[i].type);
+               return ret;
+       }
+
+       static void structure_free(struct value val)
+       {
+               int i;
+
+               if (val.structure.fields)
+                       for (i = 0; i < val.type->structure.nfields; i++)
+                               free_value(val.structure.fields[i]);
+               free(val.structure.fields);
+       }
+
+       static void structure_free_type(struct type *t)
+       {
+               int i;
+               for (i = 0; i < t->structure.nfields; i++)
+                       free_value(t->structure.fields[i].init);
+               free(t->structure.fields);
+       }
+
+       static struct type structure_prototype = {
+               .prepare = structure_prepare,
+               .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;
+
+###### 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, 0);
+               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->prepare != structure_prepare)
+                       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);
+                               *ok = 0;
+                       }
+               }
+               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);
+                               *ok = 0;
+                       }
+                       return ft;
+               }
+               break;
+       }
+
+###### interp exec cases
+       case Xfieldref:
+       {
+               struct fieldref *f = cast(fieldref, e);
+               struct value *lleft = linterp_exec(f->left);
+               lrv = &lleft->structure.fields[f->index];
+               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);
+               free_value(f->f.init);
+               free(f);
+       }
+
+###### top level grammar
+       DeclareStruct -> struct IDENTIFIER FieldBlock ${ {
+               struct type *t =
+                       add_type(config2context(config), $2.txt, &structure_prototype);
+               int cnt = 0;
+               struct fieldlist *f;
+
+               for (f = $3; f; f=f->prev)
+                       cnt += 1;
+
+               t->structure.nfields = cnt;
+               t->structure.fields = calloc(cnt, sizeof(struct field));
+               f = $3;
+               while (cnt > 0) {
+                       cnt -= 1;
+                       t->structure.fields[cnt] = f->f;
+                       f->f.init = val_prepare(Tnone);
+                       f = f->prev;
+               }
+       } }$
+
+       $*fieldlist
+       FieldBlock -> Open SimpleFieldList Close ${ $0 = $<2; }$
+               | Open Newlines SimpleFieldList Close ${ $0 = $<3; }$
+               | : FieldList  ${ $0 = $<2; }$
+
+       FieldList -> Field NEWLINE ${ $0 = $<1; }$
+               | FieldList NEWLINE ${ $0 = $<1; }$
+               | FieldList Field NEWLINE ${
+                       $2->prev = $<1;
+                       $0 = $<2;
+               }$
+
+       SimpleFieldList -> Field ; ${ $0 = $<1; }$
+               | SimpleFieldList Field ; ${
+                       $2->prev = $<1;
+                       $0 = $<2;
+               }$
+
+       Field -> IDENTIFIER : Type = Expression ${ {
+                       int ok;
+
+                       $0 = calloc(1, sizeof(struct fieldlist));
+                       $0->f.name = $1.txt;
+                       $0->f.type = $<3;
+                       $0->f.init = val_prepare($0->f.type);
+                       do {
+                               ok = 1;
+                               propagate_types($<5, config2context(config), &ok, $3, 0);
+                       } while (ok == 2);
+                       if (!ok)
+                               config2context(config)->parse_error = 1;
+                       else
+                               $0->f.init = interp_exec($5);
+               } }$
+               | IDENTIFIER : Type ${
+                       $0 = calloc(1, sizeof(struct fieldlist));
+                       $0->f.name = $1.txt;
+                       $0->f.type = $<3;
+                       $0->f.init = val_init($3);
+               }$
+
+###### 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->init.type->print) {
+                               fprintf(f, " = ");
+                               if (fl->init.type == Tstr)
+                                       fprintf(f, "\"");
+                               print_value(fl->init);
+                               if (fl->init.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;
+                       }
+               }
+       }
+
+## 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.
 
 interpreted, and freed.  There are several, so let's just start with
 the easy ones and work our way up.
 
@@ -1608,16 +2187,16 @@ an executable.
        }
 
 ###### propagate exec cases
        }
 
 ###### propagate exec cases
-               case Xval:
-               {
-                       struct val *val = cast(val, prog);
-                       if (!type_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->val.type, rules)) {
+                       type_err(c, "error: expected %1%r found %2",
+                                  prog, type, rules, val->val.type);
+                       *ok = 0;
                }
                }
+               return val->val.type;
+       }
 
 ###### interp exec cases
        case Xval:
 
 ###### interp exec cases
        case Xval:
@@ -1688,9 +2267,9 @@ link to find the primary instance.
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
+                                $0, NULL, 0, NULL);
                        type_err(config2context(config), "info: this is where '%v' was first declared",
                        type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
            | IDENTIFIER :: ${ {
                }
        } }$
            | IDENTIFIER :: ${ {
@@ -1704,9 +2283,9 @@ link to find the primary instance.
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
+                                $0, NULL, 0, NULL);
                        type_err(config2context(config), "info: this is where '%v' was first declared",
                        type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
            | IDENTIFIER : Type ${ {
                }
        } }$
            | IDENTIFIER : Type ${ {
@@ -1721,9 +2300,9 @@ link to find the primary instance.
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
+                                $0, NULL, 0, NULL);
                        type_err(config2context(config), "info: this is where '%v' was first declared",
                        type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
            | IDENTIFIER :: Type ${ {
                }
        } }$
            | IDENTIFIER :: Type ${ {
@@ -1739,9 +2318,9 @@ link to find the primary instance.
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
+                                $0, NULL, 0, NULL);
                        type_err(config2context(config), "info: this is where '%v' was first declared",
                        type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
 
                }
        } }$
 
@@ -1793,9 +2372,9 @@ link to find the primary instance.
                                struct binding *b = v->var->name;
                                fprintf(stderr, "%.*s", b->name.len, b->name.txt);
                        } else
                                struct binding *b = v->var->name;
                                fprintf(stderr, "%.*s", b->name.len, b->name.txt);
                        } else
-                               fputs("???", stderr);
+                               fputs("???", stderr);   // NOTEST
                } else
                } else
-                       fputs("NOTVAR", stderr);
+                       fputs("NOTVAR", stderr);        // NOTEST
                break;
 
 ###### propagate exec cases
                break;
 
 ###### propagate exec cases
@@ -1805,9 +2384,9 @@ link to find the primary instance.
                struct var *var = cast(var, prog);
                struct variable *v = var->var;
                if (!v) {
                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
+                       *ok = 0;                                        // NOTEST
+                       return Tnone;                                   // NOTEST
                }
                if (v->merged)
                        v = v->merged;
                }
                if (v->merged)
                        v = v->merged;
@@ -1831,7 +2410,7 @@ link to find the primary instance.
                        type_err(c, "error: expected %1%r but variable '%v' is %2", prog,
                                 type, rules, v->val.type);
                        type_err(c, "info: this is where '%v' was set to %1", v->where_set,
                        type_err(c, "error: expected %1%r but variable '%v' is %2", prog,
                                 type, rules, v->val.type);
                        type_err(c, "info: this is where '%v' was set to %1", v->where_set,
-                                v->val.type, rules, Tnone);
+                                v->val.type, rules, NULL);
                        *ok = 0;
                }
                if (!type)
                        *ok = 0;
                }
                if (!type)
@@ -1839,38 +2418,108 @@ link to find the primary instance.
                return type;
        }
 
                return type;
        }
 
-###### interp exec cases
-       case Xvar:
-       {
-               struct var *var = cast(var, e);
-               struct variable *v = var->var;
+###### interp exec cases
+       case Xvar:
+       {
+               struct var *var = cast(var, e);
+               struct variable *v = var->var;
+
+               if (v->merged)
+                       v = v->merged;
+               lrv = &v->val;
+               break;
+       }
+
+###### ast functions
+
+       static void free_var(struct var *v)
+       {
+               free(v);
+       }
+
+###### 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 operatior, so it gets to define
+what an "Expression" is.  The next level up is "BoolExpr", which
+comes next.
+
+Conditional expressions are of the form "value `if` condition `else`
+other_value".  They associate to the right, so everything to the right
+of `else` is part of an else value, while only the BoolExpr to the
+left of `if` is the if values.  Between `if` and `else` there is no
+room for ambiguity, so a full conditional expression is allowed in there.
+
+###### Binode types
+       CondExpr,
+
+###### Grammar
+
+       $*exec
+       Expression -> BoolExpr if Expression else Expression ${ {
+                       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;
+               } }$
+               | BoolExpr ${ $0 = $<1; }$
+
+###### print binode cases
 
 
-               if (v->merged)
-                       v = v->merged;
-               lrv = &v->val;
+       case CondExpr:
+               b2 = cast(binode, b->right);
+               print_exec(b2->left, -1, 0);
+               printf(" if ");
+               print_exec(b->left, -1, 0);
+               printf(" else ");
+               print_exec(b2->right, -1, 0);
                break;
                break;
-       }
 
 
-###### ast functions
+###### propagate binode cases
 
 
-       static void free_var(struct var *v)
-       {
-               free(v);
+       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;
        }
 
        }
 
-###### free exec cases
-       case Xvar: free_var(cast(var, e)); break;
+###### interp binode cases
+
+       case CondExpr: {
+               struct binode *b2 = cast(binode, b->right);
+               left = interp_exec(b->left);
+               if (left.bool)
+                       rv = interp_exec(b2->left);
+               else
+                       rv = interp_exec(b2->right);
+               }
+               break;
 
 ### Expressions: Boolean
 
 
 ### 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 for each precedence
-level used by expressions.  The outer most or lowest level precedence
-are Boolean operators which form an `Expression` out of `BTerm`s and
-`BFact`s.  As well as `or` `and`, and `not` we have `and then` and `or
-else` which only evaluate the second operand if the result would make
-a difference.
+The next class of expressions to use the `binode` will be Boolean
+expressions.  As I haven't implemented precedence in the parser
+generator yet, we need different names for each precedence level used
+by expressions.  The outer most or lowest level precedence after
+conditional expressions are Boolean operators which form an `BoolExpr`
+out of `BTerm`s and `BFact`s.  As well as `or` `and`, and `not` we
+have `and then` and `or else` which only evaluate the second operand
+if the result would make a difference.
 
 ###### Binode types
        And,
 
 ###### Binode types
        And,
@@ -1882,14 +2531,14 @@ a difference.
 ###### Grammar
 
        $*exec
 ###### Grammar
 
        $*exec
-       Expression -> Expression or BTerm ${ {
+       BoolExpr -> BoolExpr or BTerm ${ {
                        struct binode *b = new(binode);
                        b->op = Or;
                        b->left = $<1;
                        b->right = $<3;
                        $0 = b;
                } }$
                        struct binode *b = new(binode);
                        b->op = Or;
                        b->left = $<1;
                        b->right = $<3;
                        $0 = b;
                } }$
-               | Expression or else BTerm ${ {
+               | BoolExpr or else BTerm ${ {
                        struct binode *b = new(binode);
                        b->op = OrElse;
                        b->left = $<1;
                        struct binode *b = new(binode);
                        b->op = OrElse;
                        b->left = $<1;
@@ -1994,7 +2643,7 @@ a difference.
 
 Of slightly higher precedence that Boolean expressions are
 Comparisons.
 
 Of slightly higher precedence that Boolean expressions are
 Comparisons.
-A comparison takes arguments of any type, but the two types must be
+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
 the same.
 
 To simplify the parsing we introduce an `eop` which can record an
@@ -2022,11 +2671,11 @@ expression operator.
 
 ###### other BFact
        | Expr CMPop Expr ${ {
 
 ###### other BFact
        | Expr CMPop Expr ${ {
-                       struct binode *b = new(binode);
-                       b->op = $2.op;
-                       b->left = $<1;
-                       b->right = $<3;
-                       $0 = b;
+               struct binode *b = new(binode);
+               b->op = $2.op;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
        } }$
        | Expr ${ $0 = $<1; }$
 
        } }$
        | Expr ${ $0 = $<1; }$
 
@@ -2056,7 +2705,7 @@ expression operator.
                case GtrEq:  printf(" >= "); break;
                case Eql:    printf(" == "); break;
                case NEql:   printf(" != "); break;
                case GtrEq:  printf(" >= "); break;
                case Eql:    printf(" == "); break;
                case NEql:   printf(" != "); break;
-               default: abort();
+               default: abort();               // NOTEST
                }
                print_exec(b->right, -1, 0);
                break;
                }
                print_exec(b->right, -1, 0);
                break;
@@ -2104,7 +2753,7 @@ expression operator.
                case GtrEq:     rv.bool = cmp >= 0; break;
                case Eql:       rv.bool = cmp == 0; break;
                case NEql:      rv.bool = cmp != 0; break;
                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;
        }
                }
                break;
        }
@@ -2193,8 +2842,8 @@ precedence is handled better I might be able to discard this.
                case Divide: fputs(" / ", stdout); break;
                case Rem:    fputs(" % ", stdout); break;
                case Concat: fputs(" ++ ", stdout); break;
                case Divide: fputs(" / ", stdout); break;
                case Rem:    fputs(" % ", stdout); break;
                case Concat: fputs(" ++ ", stdout); break;
-               default: abort();
-               }
+               default: abort();       // NOTEST
+               }                       // NOTEST
                print_exec(b->right, indent, 0);
                break;
        case Absolute:
                print_exec(b->right, indent, 0);
                break;
        case Absolute:
@@ -2312,12 +2961,11 @@ precedence is handled better I might be able to discard this.
                return rv;
        }
 
                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.
 ### 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.
 
 Statements often come in sequences and we have corresponding simple
 statement lists and complex statement lists.
@@ -2616,7 +3264,8 @@ it is declared, and error will be raised as the name is created as
 
        | VariableDecl ${
                        if ($1->var->where_set == NULL) {
 
        | VariableDecl ${
                        if ($1->var->where_set == NULL) {
-                               type_err(config2context(config), "Variable declared with no type or value: %v",
+                               type_err(config2context(config),
+                                        "Variable declared with no type or value: %v",
                                         $1, NULL, 0, NULL);
                        } else {
                                $0 = new(binode);
                                         $1, NULL, 0, NULL);
                        } else {
                                $0 = new(binode);
@@ -2684,7 +3333,7 @@ it is declared, and error will be raised as the name is created as
                        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.",
                        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)
                } else {
                        t = propagate_types(b->right, c, ok, NULL, Rnolabel);
                        if (t)
@@ -2708,7 +3357,7 @@ it is declared, and error will be raised as the name is created as
                        free_value(*lleft);
                        *lleft = right;
                } else
                        free_value(*lleft);
                        *lleft = right;
                } else
-                       free_value(right);
+                       free_value(right);      // NOTEST
                right.type = NULL;
                break;
 
                right.type = NULL;
                break;
 
@@ -3243,215 +3892,140 @@ defined.
                break;
        }
 
                break;
        }
 
-## Complex types
-
-Now that we have the shape of the interpreter in place we can add some
-complex types and connected them in to the data structures and the
-different phases of parse, analyse, print, interpret.
-
-For now, just arrays.
-
-### 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 an arbitrary expression which is evaluated when the name
-comes into scope.
-
-Arrays cannot be assigned.  When pointers are introduced we will also
-introduce array slices which can refer to part or all of an array -
-the assignment syntax will create a slice.  For now, an array can only
-ever be referenced by the name it is declared with.  It is likely that
-a "`copy`" primitive will eventually be define which can be used to
-make a copy of an array with controllable depth.
-
-###### type union fields
-
-       struct {
-               int size;
-               struct variable *vsize;
-               struct type *member;
-       } array;
-
-###### value union fields
-       struct {
-               struct value *elmnts;
-       } array;
-
-###### value functions
-
-       static struct value array_prepare(struct type *type)
-       {
-               struct value ret;
-
-               ret.type = type;
-               ret.array.elmnts = NULL;
-               return ret;
-       }
-
-       static struct value array_init(struct type *type)
-       {
-               struct value ret;
-               int i;
-
-               ret.type = type;
-               if (type->array.vsize) {
-                       mpz_t q;
-                       mpz_init(q);
-                       mpz_tdiv_q(q, mpq_numref(type->array.vsize->val.num),
-                                  mpq_denref(type->array.vsize->val.num));
-                       type->array.size = mpz_get_si(q);
-                       mpz_clear(q);
-               }
-               ret.array.elmnts = calloc(type->array.size,
-                                         sizeof(ret.array.elmnts[0]));
-               for (i = 0; ret.array.elmnts && i < type->array.size; i++)
-                       ret.array.elmnts[i] = val_init(type->array.member);
-               return ret;
-       }
-
-       static void array_free(struct value val)
-       {
-               int i;
+### Top level structure
 
 
-               if (val.array.elmnts)
-                       for (i = 0; i < val.type->array.size; i++)
-                               free_value(val.array.elmnts[i]);
-               free(val.array.elmnts);
-       }
+All the language elements so far can be used in various places.  Now
+it is time to clarify what those places are.
 
 
-       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 (require->array.vsize == NULL && have->array.vsize == NULL)
-                       return require->array.size == have->array.size;
+At the top level of a file there will be a number of declarations.
+Many of the things that can be declared haven't been described yet,
+such as functions, procedures, imports, and probably more.
+For now there are two sorts of things that can appear at the top
+level.  They are predefined constants, `struct` types, and the main
+program.  While the syntax will allow the main program to appear
+multiple times, that will trigger an error if it is actually attempted.
 
 
-               return require->array.vsize == have->array.vsize;
-       }
+The various declarations do not return anything.  They store the
+various declarations in the parse context.
 
 
-       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]", b->name.len, b->name.txt);
-               } else
-                       fprintf(f, "%d]", type->array.size);
-               type_print(type->array.member, f);
-       }
+###### Parser: grammar
 
 
-       static struct type array_prototype = {
-               .prepare = array_prepare,
-               .init = array_init,
-               .print_type = array_print_type,
-               .compat = array_compat,
-               .free = array_free,
-       };
+       $void
+       Ocean -> DeclarationList
+
+       DeclarationList -> Declaration
+               | DeclarationList Declaration
+
+       Declaration -> DeclareConstant
+               | DeclareProgram
+               | DeclareStruct
+               | NEWLINE
+
+       ## top level 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
+
+       DeclareConstant -> const Open ConstList Close
+               | const Open Newlines ConstList Close
+               | const Open SimpleConstList }
+               | const Open Newlines SimpleConstList }
+               | const : ConstList
+               | const SimpleConstList
+
+       ConstList -> ComplexConsts
+       ComplexConsts -> ComplexConst ComplexConsts
+               | ComplexConst
+       ComplexConst -> SimpleConstList NEWLINE
+       SimpleConstList -> Const ; SimpleConstList
+               | Const
+               | Const ; SimpleConstList ;
 
 
-###### type grammar
+       $*type
+       CType -> Type   ${ $0 = $<1; }$
+               |       ${ $0 = NULL; }$
+       $void
+       Const -> IDENTIFIER :: CType = Expression ${ {
+               int ok;
+               struct variable *v;
 
 
-       | [ NUMBER ] Type ${
-               $0 = calloc(1, sizeof(struct type));
-               *($0) = array_prototype;
-               $0->array.member = $<4;
-               $0->array.vsize = NULL;
-               {
-               char tail[3];
-               mpq_t num;
-               if (number_parse(num, tail, $2.txt) == 0)
-                       tok_err(config2context(config), "error: unrecognised number", &$2);
-               else if (tail[0])
-                       tok_err(config2context(config), "error: unsupported number suffix", &$2);
-               else {
-                       $0->array.size = mpz_get_ui(mpq_numref(num));
-                       if (mpz_cmp_ui(mpq_denref(num), 1) != 0) {
-                               tok_err(config2context(config), "error: array size must be an integer",
-                                       &$2);
-                       } else if (mpz_cmp_ui(mpq_numref(num), 1UL << 30) >= 0)
-                               tok_err(config2context(config), "error: array size is too large",
-                                       &$2);
+               v = var_decl(config2context(config), $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(config2context(config), $1.txt);
+                       tok_err(config2context(config), "error: name already declared", &$1);
+                       type_err(config2context(config), "info: this is where '%v' was first declared",
+                                v->where_decl, NULL, 0, NULL);
                }
                }
+               do {
+                       ok = 1;
+                       propagate_types($5, config2context(config), &ok, $3, 0);
+               } while (ok == 2);
+               if (!ok)
+                       config2context(config)->parse_error = 1;
+               else if (v) {
+                       v->val = interp_exec($5);
                }
                }
-       }$
-
-       | [ IDENTIFIER ] Type ${ {
-               struct variable *v = var_ref(config2context(config), $2.txt);
-
-               if (!v)
-                       tok_err(config2context(config), "error: name undeclared", &$2);
-               else if (!v->constant)
-                       tok_err(config2context(config), "error: array size must be a constant", &$2);
-
-               $0 = calloc(1, sizeof(struct type));
-               *($0) = array_prototype;
-               $0->array.member = $<4;
-               $0->array.size = 0;
-               $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, 0);
-               printf("[");
-               print_exec(b->right, -1, 0);
-               printf("]");
-               break;
+###### 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;
+                               }
 
 
-###### 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);
-                       *ok = 0;
-                       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);
-                               *ok = 0;
+                       if (target == -1) {
+                               if (i)
+                                       printf("const:\n");
+                               target = i;
+                       } else {
+                               printf("    %.*s :: ", v->name->name.len, v->name->name.txt);
+                               type_print(v->val.type, stdout);
+                               printf(" = ");
+                               if (v->val.type == Tstr)
+                                       printf("\"");
+                               print_value(v->val);
+                               if (v->val.type == Tstr)
+                                       printf("\"");
+                               printf("\n");
+                               target -= 1;
                        }
                        }
-                       return t->array.member;
                }
                }
-               break;
-
-###### interp binode cases
-       case Index: {
-               mpz_t q;
-               long i;
-
-               lleft = linterp_exec(b->left);
-               right = interp_exec(b->right);
-               mpz_init(q);
-               mpz_tdiv_q(q, mpq_numref(right.num), mpq_denref(right.num));
-               i = mpz_get_si(q);
-               mpz_clear(q);
-
-               if (i >= 0 && i < lleft->type->array.size)
-                       lrv = &lleft->array.elmnts[i];
-               else
-                       rv = val_init(lleft->type->array.member);
-               break;
        }
 
 ### Finally the whole program.
        }
 
 ### Finally the whole program.
@@ -3459,7 +4033,8 @@ make a copy of an array with controllable depth.
 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
 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.
+is the code to execute.  Unlike Pascal, constants and other
+declarations come *before* the program.
 
 As this is the top level, several things are handled a bit
 differently.
 
 As this is the top level, several things are handled a bit
 differently.
@@ -3470,7 +4045,17 @@ analysis is a bit more interesting at this level.
 ###### Binode types
        Program,
 
 ###### Binode types
        Program,
 
-###### Parser: grammar
+###### top level grammar
+
+       DeclareProgram -> Program ${ {
+               struct parse_context *c = config2context(config);
+               if (c->prog)
+                       type_err(c, "Program defined a second time",
+                                $1, NULL, 0, NULL);
+               else
+                       c->prog = $<1;
+       } }$
+
 
        $*binode
        Program -> program OpenScope Varlist Block OptNL ${
 
        $*binode
        Program -> program OpenScope Varlist Block OptNL ${
@@ -3520,7 +4105,7 @@ analysis is a bit more interesting at this level.
                break;
 
 ###### propagate binode cases
                break;
 
 ###### propagate binode cases
-       case Program: abort();
+       case Program: abort();          // NOTEST
 
 ###### core functions
 
 
 ###### core functions
 
@@ -3530,7 +4115,7 @@ analysis is a bit more interesting at this level.
                int ok = 1;
 
                if (!b)
                int ok = 1;
 
                if (!b)
-                       return 0;
+                       return 0;       // NOTEST
                do {
                        ok = 1;
                        propagate_types(b->right, c, &ok, Tnone, 0);
                do {
                        ok = 1;
                        propagate_types(b->right, c, &ok, Tnone, 0);
@@ -3565,7 +4150,7 @@ analysis is a bit more interesting at this level.
                struct value v;
 
                if (!prog)
                struct value v;
 
                if (!prog)
-                       return;
+                       return;         // NOTEST
                al = cast(binode, p->left);
                while (al) {
                        struct var *v = cast(var, al->left);
                al = cast(binode, p->left);
                while (al) {
                        struct var *v = cast(var, al->left);
@@ -3587,14 +4172,15 @@ analysis is a bit more interesting at this level.
        }
 
 ###### interp binode cases
        }
 
 ###### interp binode cases
-       case Program: abort();
+       case Program: abort();  // NOTEST
 
 ## And now to test it out.
 
 
 ## 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
 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
 
 ###### File: oceani.mk
        tests :: sayhello
@@ -3604,8 +4190,23 @@ Fibonacci, and performs a binary search for a number.
 
 ###### test: hello
 
 
 ###### test: hello
 
+       const:
+               pi ::= 3.1415926
+               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
+
        program A B:
                print "Hello World, what lovely oceans you have!"
        program A B:
                print "Hello World, what lovely oceans you have!"
+               print "Are there", five, "?"
+               print pi, pie, "but", cake
+
                /* When a variable is defined in both branches of an 'if',
                 * and used afterwards, the variables are merged.
                 */
                /* When a variable is defined in both branches of an 'if',
                 * and used afterwards, the variables are merged.
                 */
@@ -3672,16 +4273,19 @@ Fibonacci, and performs a binary search for a number.
                case GiveUp:
                        print "Closest I found was", mid
 
                case GiveUp:
                        print "Closest I found was", mid
 
-               size::=55
+               size::= 10
                list:[size]number
                list[0] = 1234
                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) % 10000
 
                for i:=1; then i = i + 1; while i < size:
                        n := list[i-1] * list[i-1]
                        list[i] = (n / 100) % 10000
 
-               print "Before sort:"
+               print "Before sort:",
                for i:=0; then i = i + 1; while i < size:
                for i:=0; then i = i + 1; while i < size:
-                       print "list[",i,"]=",list[i]
+                       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:
 
                for i := 1; then i=i+1; while i < size:
                        for j:=i-1; then j=j-1; while j >= 0:
@@ -3689,6 +4293,12 @@ Fibonacci, and performs a binary search for a number.
                                        t:= list[j]
                                        list[j] = list[j+1]
                                        list[j+1] = t
                                        t:= list[j]
                                        list[j] = list[j+1]
                                        list[j+1] = t
-               print "After sort:"
+               print " After sort:",
                for i:=0; then i = i + 1; while i < size:
                for i:=0; then i = i + 1; while i < size:
-                       print "list[",i,"]=",list[i]
+                       print "", list[i],
+               print
+
+               bob:fred
+               bob.name = "Hello"
+               bob.alive = (bob.name == "Hello")
+               print "bob", "is" if  bob.alive else "isn't", "alive"