]> ocean-lang.org Git - ocean/blobdiff - csrc/oceani.mdc
oceani: add conditional expression
[ocean] / csrc / oceani.mdc
index 4f3977592947920ca2b5e4fe0d48a1ad95be69f0..07caefafca523bf1014752bf1cc228f0f7689a72 100644 (file)
@@ -1,8 +1,8 @@
 # Ocean Interpreter - Stoney Creek version
 
 # Ocean Interpreter - Stoney Creek version
 
-Ocean is intended to be an compiled language, so this interpreter is
+Ocean is intended to be a compiled language, so this interpreter is
 not targeted at being the final product.  It is, rather, an intermediate
 not targeted at being the final product.  It is, rather, an intermediate
-stage, and fills that role in two distinct ways.
+stage and fills that role in two distinct ways.
 
 Firstly, it exists as a platform to experiment with the early language
 design.  An interpreter is easy to write and easy to get working, so
 
 Firstly, it exists as a platform to experiment with the early language
 design.  An interpreter is easy to write and easy to get working, so
@@ -47,7 +47,7 @@ Elements which are present to make a usable language are:
 
  - "blocks" of multiple statements.
  - `pass`: a statement which does nothing.
 
  - "blocks" of multiple statements.
  - `pass`: a statement which does nothing.
- - expressions: `+`, `-`, `*`, `/` can apply to numbers and `++` can
+ - 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.
    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.
@@ -73,10 +73,10 @@ out the program from the parsed internal structure.  This is useful
 for validating the parsing.
 So the main requirements of the interpreter are:
 
 for validating the parsing.
 So the main requirements of the interpreter are:
 
-- Parse the program, possible with tracing
-- Analyse the parsed program to ensure consistency
-- print the program
-- execute the program
+- Parse the program, possibly with tracing,
+- Analyse the parsed program to ensure consistency,
+- Print the program,
+- Execute the program.
 
 This is all performed by a single C program extracted with
 `parsergen`.
 
 This is all performed by a single C program extracted with
 `parsergen`.
@@ -88,6 +88,9 @@ alternate section can be requested so that a file (such as this one)
 can contain multiple programs This is effected with the `--section`
 option.
 
 can contain multiple programs This is effected with the `--section`
 option.
 
+This code must be compiled with `-fplan9-extensions` so that anonymous
+structures can be used.
+
 ###### File: oceani.mk
 
        myCFLAGS := -Wall -g -fplan9-extensions
 ###### File: oceani.mk
 
        myCFLAGS := -Wall -g -fplan9-extensions
@@ -208,6 +211,9 @@ option.
                                argv[optind]);
                        exit(1);
                }
                                argv[optind]);
                        exit(1);
                }
+
+               ## context initialization
+
                if (section) {
                        struct section *ss;
                        for (ss = s; ss; ss = ss->next) {
                if (section) {
                        struct section *ss;
                        for (ss = s; ss; ss = ss->next) {
@@ -250,25 +256,26 @@ option.
                        free(s);
                        s = t;
                }
                        free(s);
                        s = t;
                }
-               ## free context
+               ## free context vars
+               ## free context types
                exit(context.parse_error ? 1 : 0);
        }
 
 ### Analysis
 
                exit(context.parse_error ? 1 : 0);
        }
 
 ### Analysis
 
-These four requirements of parse, analyse, print, interpret apply to
+The four requirements of parse, analyse, print, interpret apply to
 each language element individually so that is how most of the code
 will be structured.
 
 Three of the four are fairly self explanatory.  The one that requires
 a little explanation is the analysis step.
 
 each language element individually so that is how most of the code
 will be structured.
 
 Three of the four are fairly self explanatory.  The one that requires
 a little explanation is the analysis step.
 
-The current language design does not require (or even allow) the types
-of variables to be declared, but they must still have a single type.
-Different operations impose different requirements on the variables,
-for example addition requires both arguments to be numeric, and
-assignment requires the variable on the left to have the same type as
-the expression on the right.
+The current language design does not require the types of variables to
+be declared, but they must still have a single type.  Different
+operations impose different requirements on the variables, for example
+addition requires both arguments to be numeric, and assignment
+requires the variable on the left to have the same type as the
+expression on the right.
 
 Analysis involves propagating these type requirements around and
 consequently setting the type of each variable.  If any requirements
 
 Analysis involves propagating these type requirements around and
 consequently setting the type of each variable.  If any requirements
@@ -337,12 +344,18 @@ element where its type was set.  For now we will assume that each line
 of an error message indicates one location in the file, and up to 2
 types.  So we provide a `printf`-like function which takes a format, a
 language (a `struct exec` which has not yet been introduced), and 2
 of an error message indicates one location in the file, and up to 2
 types.  So we provide a `printf`-like function which takes a format, a
 language (a `struct exec` which has not yet been introduced), and 2
-types. "`$1`" reports the first type, "`$2`" reports the second.  We
+types. "`%1`" reports the first type, "`%2`" reports the second.  We
 will need a function to print the location, once we know how that is
 stored.  As will be explained later, there are sometimes extra rules for
 type matching and they might affect error messages, we need to pass those
 in too.
 
 will need a function to print the location, once we know how that is
 stored.  As will be explained later, there are sometimes extra rules for
 type matching and they might affect error messages, we need to pass those
 in too.
 
+As well as type errors, we sometimes need to report problems with
+tokens, which might be unexpected or might name a type that has not
+been defined.  For these we have `tok_err()` which reports an error
+with a given token.  Each of the error functions sets the flag in the
+context so indicate that parsing failed.
+
 ###### forward decls
 
        static void fput_loc(struct exec *loc, FILE *f);
 ###### forward decls
 
        static void fput_loc(struct exec *loc, FILE *f);
@@ -351,7 +364,7 @@ in too.
 
        static void type_err(struct parse_context *c,
                             char *fmt, struct exec *loc,
 
        static void type_err(struct parse_context *c,
                             char *fmt, struct exec *loc,
-                            enum vtype t1, int rules, enum vtype t2)
+                            struct type *t1, int rules, struct type *t2)
        {
                fprintf(stderr, "%s:", c->file_name);
                fput_loc(loc, stderr);
        {
                fprintf(stderr, "%s:", c->file_name);
                fput_loc(loc, stderr);
@@ -365,10 +378,10 @@ in too.
                        case '%': fputc(*fmt, stderr); break;
                        default: fputc('?', stderr); break;
                        case '1':
                        case '%': fputc(*fmt, stderr); break;
                        default: fputc('?', stderr); break;
                        case '1':
-                               fputs(vtype_names[t1], stderr);
+                               type_print(t1, stderr);
                                break;
                        case '2':
                                break;
                        case '2':
-                               fputs(vtype_names[t2], stderr);
+                               type_print(t2, stderr);
                                break;
                        ## format cases
                        }
                                break;
                        ## format cases
                        }
@@ -379,7 +392,8 @@ in too.
 
        static void tok_err(struct parse_context *c, char *fmt, struct token *t)
        {
 
        static void tok_err(struct parse_context *c, char *fmt, struct token *t)
        {
-               fprintf(stderr, "%s:%d:%d: %s\n", c->file_name, t->line, t->col, fmt);
+               fprintf(stderr, "%s:%d:%d: %s: %.*s\n", c->file_name, t->line, t->col, fmt,
+                       t->txt.len, t->txt.txt);
                c->parse_error = 1;
        }
 
                c->parse_error = 1;
        }
 
@@ -392,20 +406,213 @@ to store these elements.
 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
 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.
+which hold the values, and types which classify the values stored and
+manipulatd by executables.
 
 
-### Values
+### Types
+
+Values come in a wide range of types, with more likely to be added.
+Each type needs to be able to parse and print its own values (for
+convenience at least) as well as to compare two values, at least for
+equality and possibly for order.  For now, values might need to be
+duplicated and freed, though eventually such manipulations will be
+better integrated into the language.
+
+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.
+
+Named type are stored in a simple linked list.  Objects of each type are "values"
+which are often passed around by value.
+
+###### ast
+
+       struct value {
+               struct type *type;
+               union {
+                       ## value union fields
+               };
+       };
+
+       struct type {
+               struct text name;
+               struct type *next;
+               struct value (*init)(struct type *type);
+               struct value (*prepare)(struct type *type);
+               struct value (*parse)(struct type *type, char *str);
+               void (*print)(struct value val);
+               void (*print_type)(struct type *type, FILE *f);
+               int (*cmp_order)(struct value v1, struct value v2);
+               int (*cmp_eq)(struct value v1, struct value v2);
+               struct value (*dup)(struct value val);
+               void (*free)(struct value val);
+               int (*compat)(struct type *this, struct type *other);
+               long long (*to_int)(struct value *v);
+               double (*to_float)(struct value *v);
+               int (*to_mpq)(mpq_t *q, struct value *v);
+               union {
+                       ## type union fields
+               };
+       };
 
 
-Values can be numbers, which we represent as multi-precision
-fractions, strings, Booleans and labels.  When analysing the program
-we also need to allow for places where no value is meaningful
-(`Vnone`) and where we don't know what type to expect yet (`Vunknown`).
+###### parse context
+
+       struct type *typelist;
+
+###### ast functions
+
+       static struct type *find_type(struct parse_context *c, struct text s)
+       {
+               struct type *l = c->typelist;
+
+               while (l &&
+                      text_cmp(l->name, s) != 0)
+                               l = l->next;
+               return l;
+       }
+
+       static struct type *add_type(struct parse_context *c, struct text s,
+                                    struct type *proto)
+       {
+               struct type *n;
+
+               n = calloc(1, sizeof(*n));
+               *n = *proto;
+               n->name = s;
+               n->next = c->typelist;
+               c->typelist = n;
+               return n;
+       }
+
+       static void free_type(struct type *t)
+       {
+               /* The type is always a reference to something in the
+                * context, so we don't need to free anything.
+                */
+       }
+
+       static void free_value(struct value v)
+       {
+               if (v.type)
+                       v.type->free(v);
+       }
+
+       static int type_compat(struct type *require, struct type *have, int rules)
+       {
+               if ((rules & Rboolok) && have == Tbool)
+                       return 1;
+               if ((rules & Rnolabel) && have == Tlabel)
+                       return 0;
+               if (!require || !have)
+                       return 1;
+
+               if (require->compat)
+                       return require->compat(require, have);
+
+               return require == have;
+       }
+
+       static void type_print(struct type *type, FILE *f)
+       {
+               if (!type)
+                       fputs("*unknown*type*", f);
+               else if (type->name.len)
+                       fprintf(f, "%.*s", type->name.len, type->name.txt);
+               else if (type->print_type)
+                       type->print_type(type, f);
+               else
+                       fputs("*invalid*type*", f);
+       }
+
+       static struct value val_prepare(struct type *type)
+       {
+               struct value rv;
+
+               if (type)
+                       return type->prepare(type);
+               rv.type = type;
+               return rv;
+       }
+
+       static struct value val_init(struct type *type)
+       {
+               struct value rv;
+
+               if (type)
+                       return type->init(type);
+               rv.type = type;
+               return rv;
+       }
+
+       static struct value dup_value(struct value v)
+       {
+               if (v.type)
+                       return v.type->dup(v);
+               return v;
+       }
+
+       static int value_cmp(struct value left, struct value right)
+       {
+               if (left.type && left.type->cmp_order)
+                       return left.type->cmp_order(left, right);
+               if (left.type && left.type->cmp_eq)
+                       return left.type->cmp_eq(left, right);
+               return -1;
+       }
+
+       static void print_value(struct value v)
+       {
+               if (v.type && v.type->print)
+                       v.type->print(v);
+               else
+                       printf("*Unknown*");
+       }
+
+       static struct value parse_value(struct type *type, char *arg)
+       {
+               struct value rv;
+
+               if (type && type->parse)
+                       return type->parse(type, arg);
+               rv.type = NULL;
+               return rv;
+       }
+
+###### forward decls
+
+       static void free_value(struct value v);
+       static int type_compat(struct type *require, struct type *have, int rules);
+       static void type_print(struct type *type, FILE *f);
+       static struct value val_init(struct type *type);
+       static struct value dup_value(struct value v);
+       static int value_cmp(struct value left, struct value right);
+       static void print_value(struct value v);
+       static struct value parse_value(struct type *type, char *arg);
+
+###### free context types
+
+       while (context.typelist) {
+               struct type *t = context.typelist;
+
+               context.typelist = t->next;
+               free(t);
+       }
+
+#### Base Types
+
+Values of the base types can be numbers, which we represent as
+multi-precision fractions, strings, Booleans and labels.  When
+analysing the program we also need to allow for places where no value
+is meaningful (type `Tnone`) and where we don't know what type to
+expect yet (type is `NULL`).
 
 Values are never shared, they are always copied when used, and freed
 when no longer needed.
 
 When propagating type information around the program, we need to
 
 Values are never shared, they are always copied when used, and freed
 when no longer needed.
 
 When propagating type information around the program, we need to
-determine if two types are compatible, where `Vunknown` is compatible
+determine if two types are compatible, where type `NULL` is compatible
 with anything.  There are two special cases with type compatibility,
 both related to the Conditional Statement which will be described
 later.  In some cases a Boolean can be accepted as well as some other
 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
@@ -424,34 +631,20 @@ to parse each type from a string.
        myLDLIBS := libnumber.o libstring.o -lgmp
        LDLIBS := $(filter-out $(myLDLIBS),$(LDLIBS)) $(myLDLIBS)
 
        myLDLIBS := libnumber.o libstring.o -lgmp
        LDLIBS := $(filter-out $(myLDLIBS),$(LDLIBS)) $(myLDLIBS)
 
-###### ast
-       struct value {
-               enum vtype {Vunknown, Vnone, Vstr, Vnum, Vbool, Vlabel} vtype;
-               union {
-                       struct text str;
-                       mpq_t num;
-                       int bool;
-                       void *label;
-               };
-       };
-
-       enum val_rules {Rnolabel = 1<<0, Rboolok = 1<<1};
+###### type union fields
+       enum vtype {Vnone, Vstr, Vnum, Vbool, Vlabel} vtype;
 
 
-       char *vtype_names[] = {"unknown", "none", "string",
-                              "number", "Boolean", "label"};
-
-###### format cases
-       case 'r':
-               if (rules & Rnolabel)
-                       fputs(" (labels not permitted)", stderr);
-               break;
+###### value union fields
+       struct text str;
+       mpq_t num;
+       int bool;
+       void *label;
 
 ###### ast functions
 
 ###### ast functions
-       static void free_value(struct value v)
+       static void _free_value(struct value v)
        {
        {
-               switch (v.vtype) {
-               case Vnone:
-               case Vunknown: break;
+               switch (v.type->vtype) {
+               case Vnone: break;
                case Vstr: free(v.str.txt); break;
                case Vnum: mpq_clear(v.num); break;
                case Vlabel:
                case Vstr: free(v.str.txt); break;
                case Vnum: mpq_clear(v.num); break;
                case Vlabel:
@@ -459,48 +652,64 @@ to parse each type from a string.
                }
        }
 
                }
        }
 
-       static int vtype_compat(enum vtype require, enum vtype have, int rules)
+###### value functions
+
+       static struct value _val_prepare(struct type *type)
        {
        {
-               if ((rules & Rboolok) && have == Vbool)
-                       return 1;
-               if ((rules & Rnolabel) && have == Vlabel)
-                       return 0;
-               if (require == Vunknown || have == Vunknown)
-                       return 1;
+               struct value rv;
 
 
-               return require == have;
+               rv.type = type;
+               switch(type->vtype) {
+               case Vnone:
+                       break;
+               case Vnum:
+                       memset(&rv.num, 0, sizeof(rv.num));
+                       break;
+               case Vstr:
+                       rv.str.txt = NULL;
+                       rv.str.len = 0;
+                       break;
+               case Vbool:
+                       rv.bool = 0;
+                       break;
+               case Vlabel:
+                       rv.label = NULL;
+                       break;
+               }
+               return rv;
        }
 
        }
 
-###### value functions
-
-       static void val_init(struct value *val, enum vtype type)
+       static struct value _val_init(struct type *type)
        {
        {
-               val->vtype = type;
-               switch(type) {
-               case Vnone:abort();
-               case Vunknown: break;
+               struct value rv;
+
+               rv.type = type;
+               switch(type->vtype) {
+               case Vnone:
+                       break;
                case Vnum:
                case Vnum:
-                       mpq_init(val->num); break;
+                       mpq_init(rv.num); break;
                case Vstr:
                case Vstr:
-                       val->str.txt = malloc(1);
-                       val->str.len = 0;
+                       rv.str.txt = malloc(1);
+                       rv.str.len = 0;
                        break;
                case Vbool:
                        break;
                case Vbool:
-                       val->bool = 0;
+                       rv.bool = 0;
                        break;
                case Vlabel:
                        break;
                case Vlabel:
-                       val->label = val;
+                       rv.label = NULL;
                        break;
                }
                        break;
                }
+               return rv;
        }
 
        }
 
-       static struct value dup_value(struct value v)
+       static struct value _dup_value(struct value v)
        {
                struct value rv;
        {
                struct value rv;
-               rv.vtype = v.vtype;
-               switch (rv.vtype) {
+               rv.type = v.type;
+               switch (rv.type->vtype) {
                case Vnone:
                case Vnone:
-               case Vunknown: break;
+                       break;
                case Vlabel:
                        rv.label = v.label;
                        break;
                case Vlabel:
                        rv.label = v.label;
                        break;
@@ -520,37 +729,24 @@ to parse each type from a string.
                return rv;
        }
 
                return rv;
        }
 
-       static int value_cmp(struct value left, struct value right)
+       static int _value_cmp(struct value left, struct value right)
        {
                int cmp;
        {
                int cmp;
-               if (left.vtype != right.vtype)
-                       return left.vtype - right.vtype;
-               switch (left.vtype) {
+               if (left.type != right.type)
+                       return left.type - right.type;
+               switch (left.type->vtype) {
                case Vlabel: cmp = left.label == right.label ? 0 : 1; break;
                case Vnum: cmp = mpq_cmp(left.num, right.num); break;
                case Vstr: cmp = text_cmp(left.str, right.str); break;
                case Vbool: cmp = left.bool - right.bool; break;
                case 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:
-               case Vunknown: cmp = 0;
+               case Vnone: cmp = 0;
                }
                return cmp;
        }
 
                }
                return cmp;
        }
 
-       static struct text text_join(struct text a, struct text b)
-       {
-               struct text rv;
-               rv.len = a.len + b.len;
-               rv.txt = malloc(rv.len);
-               memcpy(rv.txt, a.txt, a.len);
-               memcpy(rv.txt+a.len, b.txt, b.len);
-               return rv;
-       }
-
-       static void print_value(struct value v)
+       static void _print_value(struct value v)
        {
        {
-               switch (v.vtype) {
-               case Vunknown:
-                       printf("*Unknown*"); break;
+               switch (v.type->vtype) {
                case Vnone:
                        printf("*no-value*"); break;
                case Vlabel:
                case Vnone:
                        printf("*no-value*"); break;
                case Vlabel:
@@ -571,21 +767,23 @@ to parse each type from a string.
                }
        }
 
                }
        }
 
-       static int parse_value(struct value *vl, char *arg)
+       static struct value _parse_value(struct type *type, char *arg)
        {
        {
+               struct value val;
                struct text tx;
                int neg = 0;
                char tail[3] = "";
 
                struct text tx;
                int neg = 0;
                char tail[3] = "";
 
-               switch(vl->vtype) {
+               val.type = type;
+               switch(type->vtype) {
                case Vlabel:
                case Vlabel:
-               case Vunknown:
                case Vnone:
                case Vnone:
-                       return 0;
+                       val.type = NULL;
+                       break;
                case Vstr:
                case Vstr:
-                       vl->str.len = strlen(arg);
-                       vl->str.txt = malloc(vl->str.len);
-                       memcpy(vl->str.txt, arg, vl->str.len);
+                       val.str.len = strlen(arg);
+                       val.str.txt = malloc(val.str.len);
+                       memcpy(val.str.txt, arg, val.str.len);
                        break;
                case Vnum:
                        if (*arg == '-') {
                        break;
                case Vnum:
                        if (*arg == '-') {
@@ -593,31 +791,65 @@ to parse each type from a string.
                                arg++;
                        }
                        tx.txt = arg; tx.len = strlen(tx.txt);
                                arg++;
                        }
                        tx.txt = arg; tx.len = strlen(tx.txt);
-                       if (number_parse(vl->num, tail, tx) == 0)
-                               mpq_init(vl->num);
+                       if (number_parse(val.num, tail, tx) == 0)
+                               mpq_init(val.num);
                        else if (neg)
                        else if (neg)
-                               mpq_neg(vl->num, vl->num);
+                               mpq_neg(val.num, val.num);
                        if (tail[0]) {
                                printf("Unsupported suffix: %s\n", arg);
                        if (tail[0]) {
                                printf("Unsupported suffix: %s\n", arg);
-                               return 0;
+                               val.type = NULL;
                        }
                        break;
                case Vbool:
                        if (strcasecmp(arg, "true") == 0 ||
                            strcmp(arg, "1") == 0)
                        }
                        break;
                case Vbool:
                        if (strcasecmp(arg, "true") == 0 ||
                            strcmp(arg, "1") == 0)
-                               vl->bool = 1;
+                               val.bool = 1;
                        else if (strcasecmp(arg, "false") == 0 ||
                                 strcmp(arg, "0") == 0)
                        else if (strcasecmp(arg, "false") == 0 ||
                                 strcmp(arg, "0") == 0)
-                               vl->bool = 0;
+                               val.bool = 0;
                        else {
                                printf("Bad bool: %s\n", arg);
                        else {
                                printf("Bad bool: %s\n", arg);
-                               return 0;
+                               val.type = NULL;
                        }
                        break;
                }
                        }
                        break;
                }
-               return 1;
+               return val;
        }
 
        }
 
+       static void _free_value(struct value v);
+
+       static struct type base_prototype = {
+               .init = _val_init,
+               .prepare = _val_prepare,
+               .parse = _parse_value,
+               .print = _print_value,
+               .cmp_order = _value_cmp,
+               .cmp_eq = _value_cmp,
+               .dup = _dup_value,
+               .free = _free_value,
+       };
+
+       static struct type *Tbool, *Tstr, *Tnum, *Tnone, *Tlabel;
+
+###### ast functions
+       static struct type *add_base_type(struct parse_context *c, char *n, enum vtype vt)
+       {
+               struct text txt = { n, strlen(n) };
+               struct type *t;
+
+               t = add_type(c, txt, &base_prototype);
+               t->vtype = vt;
+               return t;
+       }
+
+###### context initialization
+
+       Tbool  = add_base_type(&context, "Boolean", Vbool);
+       Tstr   = add_base_type(&context, "string", Vstr);
+       Tnum   = add_base_type(&context, "number", Vnum);
+       Tnone  = add_base_type(&context, "none", Vnone);
+       Tlabel = add_base_type(&context, "label", Vlabel);
+
 ### Variables
 
 Variables are scoped named values.  We store the names in a linked
 ### Variables
 
 Variables are scoped named values.  We store the names in a linked
@@ -688,7 +920,7 @@ it is constant
 
 Scopes in parallel branches can be partially merged.  More
 specifically, if a given name is declared in both branches of an
 
 Scopes in parallel branches can be partially merged.  More
 specifically, if a given name is declared in both branches of an
-if/else then it's scope is a candidate for merging.  Similarly if
+if/else then its scope is a candidate for merging.  Similarly if
 every branch of an exhaustive switch (e.g. has an "else" clause)
 declares a given name, then the scopes from the branches are
 candidates for merging.
 every branch of an exhaustive switch (e.g. has an "else" clause)
 declares a given name, then the scopes from the branches are
 candidates for merging.
@@ -838,7 +1070,7 @@ no longer be primary.
                        }
        }
 
                        }
        }
 
-###### free context
+###### free context vars
 
        while (context.varlist) {
                struct binding *b = context.varlist;
 
        while (context.varlist) {
                struct binding *b = context.varlist;
@@ -924,7 +1156,7 @@ all pending-scope variables become conditionally scoped.
                v->scope = InScope;
                v->in_scope = c->in_scope;
                c->in_scope = v;
                v->scope = InScope;
                v->in_scope = c->in_scope;
                c->in_scope = v;
-               val_init(&v->val, Vunknown);
+               v->val = val_prepare(NULL);
                return v;
        }
 
                return v;
        }
 
@@ -976,7 +1208,7 @@ all pending-scope variables become conditionally scoped.
                                        else if (v->previous &&
                                                 v->previous->scope == PendingScope)
                                                v->scope = PendingScope;
                                        else if (v->previous &&
                                                 v->previous->scope == PendingScope)
                                                v->scope = PendingScope;
-                                       else if (v->val.vtype == Vlabel)
+                                       else if (v->val.type == Tlabel)
                                                v->scope = PendingScope;
                                        else if (v->name->var == v)
                                                v->scope = OutScope;
                                                v->scope = PendingScope;
                                        else if (v->name->var == v)
                                                v->scope = OutScope;
@@ -993,14 +1225,14 @@ all pending-scope variables become conditionally scoped.
                                        for (v2 = v;
                                             v2 && v2->scope == PendingScope;
                                             v2 = v2->previous)
                                        for (v2 = v;
                                             v2 && v2->scope == PendingScope;
                                             v2 = v2->previous)
-                                               if (v2->val.vtype != Vlabel)
+                                               if (v2->val.type != Tlabel)
                                                        v2->scope = OutScope;
                                        break;
                                case OutScope: break;
                                }
                                break;
                        case CloseSequential:
                                                        v2->scope = OutScope;
                                        break;
                                case OutScope: break;
                                }
                                break;
                        case CloseSequential:
-                               if (v->val.vtype == Vlabel)
+                               if (v->val.type == Tlabel)
                                        v->scope = PendingScope;
                                switch (v->scope) {
                                case InScope:
                                        v->scope = PendingScope;
                                switch (v->scope) {
                                case InScope:
@@ -1015,7 +1247,7 @@ all pending-scope variables become conditionally scoped.
                                        for (v2 = v;
                                             v2 && v2->scope == PendingScope;
                                             v2 = v2->previous)
                                        for (v2 = v;
                                             v2 && v2->scope == PendingScope;
                                             v2 = v2->previous)
-                                               if (v2->val.vtype == Vlabel) {
+                                               if (v2->val.type == Tlabel) {
                                                        v2->scope = CondScope;
                                                        v2->min_depth = c->scope_depth;
                                                } else
                                                        v2->scope = CondScope;
                                                        v2->min_depth = c->scope_depth;
                                                } else
@@ -1084,6 +1316,8 @@ subclasses, and to access these we need to be able to `cast` the
 
        static int __fput_loc(struct exec *loc, FILE *f)
        {
 
        static int __fput_loc(struct exec *loc, FILE *f)
        {
+               if (!loc)
+                       return 0;
                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;
@@ -1184,22 +1418,33 @@ also want to know what sort of bracketing to use.
 As discussed, analysis involves propagating type requirements around
 the program and looking for errors.
 
 As discussed, analysis involves propagating type requirements around
 the program and looking for errors.
 
-So `propagate_types` is passed an expected type (being a `vtype`
-together with some `val_rules` flags) that the `exec` is expected to
-return, and returns the type that it does return, either of which can
-be `Vunknown`.  An `ok` flag is passed by reference. It is set to `0`
-when an error is found, and `2` when any change is made.  If it
-remains unchanged at `1`, then no more propagation is needed.
+So `propagate_types` is passed an expected type (being a `struct type`
+pointer together with some `val_rules` flags) that the `exec` is
+expected to return, and returns the type that it does return, either
+of which can be `NULL` signifying "unknown".  An `ok` flag is passed
+by reference. It is set to `0` when an error is found, and `2` when
+any change is made.  If it remains unchanged at `1`, then no more
+propagation is needed.
+
+###### ast
+
+       enum val_rules {Rnolabel = 1<<0, Rboolok = 1<<1, Rnoconstant = 2<<1};
+
+###### format cases
+       case 'r':
+               if (rules & Rnolabel)
+                       fputs(" (labels not permitted)", stderr);
+               break;
 
 ###### core functions
 
 
 ###### core functions
 
-       static enum vtype propagate_types(struct exec *prog, struct parse_context *c, int *ok,
-                                         enum vtype type, int rules)
+       static struct type *propagate_types(struct exec *prog, struct parse_context *c, int *ok,
+                                           struct type *type, int rules)
        {
        {
-               enum vtype t;
+               struct type *t;
 
                if (!prog)
 
                if (!prog)
-                       return Vnone;
+                       return Tnone;
 
                switch (prog->type) {
                case Xbinode:
 
                switch (prog->type) {
                case Xbinode:
@@ -1212,7 +1457,7 @@ remains unchanged at `1`, then no more propagation is needed.
                }
                ## propagate exec cases
                }
                }
                ## propagate exec cases
                }
-               return Vnone;
+               return Tnone;
        }
 
 #### Interpreting
        }
 
 #### Interpreting
@@ -1223,23 +1468,50 @@ 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 `Vnone` but shouldn't be `Vunknown`.
+Each `exec` can return a value, which may be `Tnone` but must be non-NULL;
 
 ###### core functions
 
 
 ###### core functions
 
+       struct lrval {
+               struct value val, *lval;
+       };
+
+       static struct lrval _interp_exec(struct exec *e);
+
        static struct value interp_exec(struct exec *e)
        {
        static struct value interp_exec(struct exec *e)
        {
-               struct value rv;
-               rv.vtype = Vnone;
-               if (!e)
-                       return rv;
+               struct lrval ret = _interp_exec(e);
+
+               if (ret.lval)
+                       return dup_value(*ret.lval);
+               else
+                       return ret.val;
+       }
+
+       static struct value *linterp_exec(struct exec *e)
+       {
+               struct lrval ret = _interp_exec(e);
+
+               return ret.lval;
+       }
+
+       static struct lrval _interp_exec(struct exec *e)
+       {
+               struct lrval ret;
+               struct value rv, *lrv = NULL;
+               rv.type = Tnone;
+               if (!e) {
+                       ret.lval = lrv;
+                       ret.val = rv;
+                       return ret;
+               }
 
                switch(e->type) {
                case Xbinode:
                {
                        struct binode *b = cast(binode, e);
 
                switch(e->type) {
                case Xbinode:
                {
                        struct binode *b = cast(binode, e);
-                       struct value left, right;
-                       left.vtype = right.vtype = Vnone;
+                       struct value left, right, *lleft;
+                       left.type = right.type = Tnone;
                        switch (b->op) {
                        ## interp binode cases
                        }
                        switch (b->op) {
                        ## interp binode cases
                        }
@@ -1248,7 +1520,9 @@ Each `exec` can return a value, which may be `Vnone` but shouldn't be `Vunknown`
                }
                ## interp exec cases
                }
                }
                ## interp exec cases
                }
-               return rv;
+               ret.lval = lrv;
+               ret.val = rv;
+               return ret;
        }
 
 ## Language elements
        }
 
 ## Language elements
@@ -1278,45 +1552,45 @@ an executable.
        $*val
        Value ->  True ${
                        $0 = new_pos(val, $1);
        $*val
        Value ->  True ${
                        $0 = new_pos(val, $1);
-                       $0->val.vtype = Vbool;
+                       $0->val.type = Tbool;
                        $0->val.bool = 1;
                        }$
                | False ${
                        $0 = new_pos(val, $1);
                        $0->val.bool = 1;
                        }$
                | False ${
                        $0 = new_pos(val, $1);
-                       $0->val.vtype = Vbool;
+                       $0->val.type = Tbool;
                        $0->val.bool = 0;
                        }$
                | NUMBER ${
                        $0 = new_pos(val, $1);
                        $0->val.bool = 0;
                        }$
                | NUMBER ${
                        $0 = new_pos(val, $1);
-                       $0->val.vtype = Vnum;
+                       $0->val.type = Tnum;
                        {
                        char tail[3];
                        if (number_parse($0->val.num, tail, $1.txt) == 0)
                                mpq_init($0->val.num);
                                if (tail[0])
                        {
                        char tail[3];
                        if (number_parse($0->val.num, tail, $1.txt) == 0)
                                mpq_init($0->val.num);
                                if (tail[0])
-                                       tok_err(config2context(config), "error: unsupported number suffix.",
+                                       tok_err(config2context(config), "error: unsupported number suffix",
                                                &$1);
                        }
                        }$
                | STRING ${
                        $0 = new_pos(val, $1);
                                                &$1);
                        }
                        }$
                | STRING ${
                        $0 = new_pos(val, $1);
-                       $0->val.vtype = Vstr;
+                       $0->val.type = Tstr;
                        {
                        char tail[3];
                        string_parse(&$1, '\\', &$0->val.str, tail);
                        if (tail[0])
                        {
                        char tail[3];
                        string_parse(&$1, '\\', &$0->val.str, tail);
                        if (tail[0])
-                               tok_err(config2context(config), "error: unsupported string suffix.",
+                               tok_err(config2context(config), "error: unsupported string suffix",
                                        &$1);
                        }
                        }$
                | MULTI_STRING ${
                        $0 = new_pos(val, $1);
                                        &$1);
                        }
                        }$
                | MULTI_STRING ${
                        $0 = new_pos(val, $1);
-                       $0->val.vtype = Vstr;
+                       $0->val.type = Tstr;
                        {
                        char tail[3];
                        string_parse(&$1, '\\', &$0->val.str, tail);
                        if (tail[0])
                        {
                        char tail[3];
                        string_parse(&$1, '\\', &$0->val.str, tail);
                        if (tail[0])
-                               tok_err(config2context(config), "error: unsupported string suffix.",
+                               tok_err(config2context(config), "error: unsupported string suffix",
                                        &$1);
                        }
                        }$
                                        &$1);
                        }
                        }$
@@ -1325,10 +1599,10 @@ an executable.
        case Xval:
        {
                struct val *v = cast(val, e);
        case Xval:
        {
                struct val *v = cast(val, e);
-               if (v->val.vtype == Vstr)
+               if (v->val.type == Tstr)
                        printf("\"");
                print_value(v->val);
                        printf("\"");
                print_value(v->val);
-               if (v->val.vtype == Vstr)
+               if (v->val.type == Tstr)
                        printf("\"");
                break;
        }
                        printf("\"");
                break;
        }
@@ -1337,17 +1611,18 @@ an executable.
                case Xval:
                {
                        struct val *val = cast(val, prog);
                case Xval:
                {
                        struct val *val = cast(val, prog);
-                       if (!vtype_compat(type, val->val.vtype, rules)) {
+                       if (!type_compat(type, val->val.type, rules)) {
                                type_err(c, "error: expected %1%r found %2",
                                type_err(c, "error: expected %1%r found %2",
-                                          prog, type, rules, val->val.vtype);
+                                          prog, type, rules, val->val.type);
                                *ok = 0;
                        }
                                *ok = 0;
                        }
-                       return val->val.vtype;
+                       return val->val.type;
                }
 
 ###### interp exec cases
        case Xval:
                }
 
 ###### interp exec cases
        case Xval:
-               return dup_value(cast(val, e)->val);
+               rv = dup_value(cast(val, e)->val);
+               break;
 
 ###### ast functions
        static void free_val(struct val *v)
 
 ###### ast functions
        static void free_val(struct val *v)
@@ -1403,7 +1678,7 @@ link to find the primary instance.
 ###### Grammar
 
        $*var
 ###### Grammar
 
        $*var
-       VariableDecl -> IDENTIFIER := ${ {
+       VariableDecl -> IDENTIFIER : ${ {
                struct variable *v = var_decl(config2context(config), $1.txt);
                $0 = new_pos(var, $1);
                $0->var = v;
                struct variable *v = var_decl(config2context(config), $1.txt);
                $0 = new_pos(var, $1);
                $0->var = v;
@@ -1413,28 +1688,64 @@ 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, Vnone, 0, Vnone);
+                                $0, Tnone, 0, Tnone);
+                       type_err(config2context(config), "info: this is where '%v' was first declared",
+                                v->where_decl, Tnone, 0, Tnone);
+               }
+       } }$
+           | IDENTIFIER :: ${ {
+               struct variable *v = var_decl(config2context(config), $1.txt);
+               $0 = new_pos(var, $1);
+               $0->var = v;
+               if (v) {
+                       v->where_decl = $0;
+                       v->constant = 1;
+               } else {
+                       v = var_ref(config2context(config), $1.txt);
+                       $0->var = v;
+                       type_err(config2context(config), "error: variable '%v' redeclared",
+                                $0, Tnone, 0, Tnone);
+                       type_err(config2context(config), "info: this is where '%v' was first declared",
+                                v->where_decl, Tnone, 0, Tnone);
+               }
+       } }$
+           | IDENTIFIER : Type ${ {
+               struct variable *v = var_decl(config2context(config), $1.txt);
+               $0 = new_pos(var, $1);
+               $0->var = v;
+               if (v) {
+                       v->where_decl = $0;
+                       v->where_set = $0;
+                       v->val = val_prepare($<3);
+               } else {
+                       v = var_ref(config2context(config), $1.txt);
+                       $0->var = v;
+                       type_err(config2context(config), "error: variable '%v' redeclared",
+                                $0, Tnone, 0, Tnone);
                        type_err(config2context(config), "info: this is where '%v' was first declared",
                        type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Vnone, 0, Vnone);
+                                v->where_decl, Tnone, 0, Tnone);
                }
        } }$
                }
        } }$
-           | IDENTIFIER ::= ${ {
+           | IDENTIFIER :: Type ${ {
                struct variable *v = var_decl(config2context(config), $1.txt);
                $0 = new_pos(var, $1);
                $0->var = v;
                if (v) {
                        v->where_decl = $0;
                struct variable *v = var_decl(config2context(config), $1.txt);
                $0 = new_pos(var, $1);
                $0->var = v;
                if (v) {
                        v->where_decl = $0;
+                       v->where_set = $0;
+                       v->val = val_prepare($<3);
                        v->constant = 1;
                } else {
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
                        v->constant = 1;
                } else {
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Vnone, 0, Vnone);
+                                $0, Tnone, 0, Tnone);
                        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, Vnone, 0, Vnone);
+                                v->where_decl, Tnone, 0, Tnone);
                }
        } }$
 
                }
        } }$
 
+       $*exec
        Variable -> IDENTIFIER ${ {
                struct variable *v = var_ref(config2context(config), $1.txt);
                $0 = new_pos(var, $1);
        Variable -> IDENTIFIER ${ {
                struct variable *v = var_ref(config2context(config), $1.txt);
                $0 = new_pos(var, $1);
@@ -1442,12 +1753,26 @@ link to find the primary instance.
                        /* This might be a label - allocate a var just in case */
                        v = var_decl(config2context(config), $1.txt);
                        if (v) {
                        /* This might be a label - allocate a var just in case */
                        v = var_decl(config2context(config), $1.txt);
                        if (v) {
-                               val_init(&v->val, Vlabel);
+                               v->val = val_prepare(Tlabel);
+                               v->val.label = &v->val;
                                v->where_set = $0;
                        }
                }
                                v->where_set = $0;
                        }
                }
-               $0->var = v;
+               cast(var, $0)->var = v;
        } }$
        } }$
+       ## variable grammar
+
+       $*type
+       Type -> IDENTIFIER ${
+               $0 = find_type(config2context(config), $1.txt);
+               if (!$0) {
+                       tok_err(config2context(config),
+                               "error: undefined type", &$1);
+
+                       $0 = Tnone;
+               }
+       }$
+       ## type grammar
 
 ###### print exec cases
        case Xvar:
 
 ###### print exec cases
        case Xvar:
@@ -1480,29 +1805,37 @@ 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, Vnone, 0, Vnone);
+                       type_err(c, "%d:BUG: no variable!!", prog, Tnone, 0, Tnone);
                        *ok = 0;
                        *ok = 0;
-                       return Vnone;
+                       return Tnone;
                }
                if (v->merged)
                        v = v->merged;
                }
                if (v->merged)
                        v = v->merged;
-               if (v->val.vtype == Vunknown) {
-                       if (type != Vunknown && *ok != 0) {
-                               val_init(&v->val, type);
+               if (v->constant && (rules & Rnoconstant)) {
+                       type_err(c, "error: Cannot assign to a constant: %v",
+                                prog, NULL, 0, NULL);
+                       type_err(c, "info: name was defined as a constant here",
+                                v->where_decl, NULL, 0, NULL);
+                       *ok = 0;
+                       return v->val.type;
+               }
+               if (v->val.type == NULL) {
+                       if (type && *ok != 0) {
+                               v->val = val_prepare(type);
                                v->where_set = prog;
                                *ok = 2;
                        }
                        return type;
                }
                                v->where_set = prog;
                                *ok = 2;
                        }
                        return type;
                }
-               if (!vtype_compat(type, v->val.vtype, rules)) {
+               if (!type_compat(type, v->val.type, rules)) {
                        type_err(c, "error: expected %1%r but variable '%v' is %2", prog,
                        type_err(c, "error: expected %1%r but variable '%v' is %2", prog,
-                                type, rules, v->val.vtype);
+                                type, rules, v->val.type);
                        type_err(c, "info: this is where '%v' was set to %1", v->where_set,
                        type_err(c, "info: this is where '%v' was set to %1", v->where_set,
-                                v->val.vtype, rules, Vnone);
+                                v->val.type, rules, Tnone);
                        *ok = 0;
                }
                        *ok = 0;
                }
-               if (type == Vunknown)
-                       return v->val.vtype;
+               if (!type)
+                       return v->val.type;
                return type;
        }
 
                return type;
        }
 
@@ -1514,7 +1847,8 @@ link to find the primary instance.
 
                if (v->merged)
                        v = v->merged;
 
                if (v->merged)
                        v = v->merged;
-               return dup_value(v->val);
+               lrv = &v->val;
+               break;
        }
 
 ###### ast functions
        }
 
 ###### ast functions
@@ -1527,30 +1861,111 @@ link to find the primary instance.
 ###### free exec cases
        case Xvar: free_var(cast(var, e)); break;
 
 ###### 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".  There is no associativite with this operator: the
+values and conditions can only be other conditional expressions if
+they are enclosed in parentheses.  Allowing nesting without
+parentheses would be too confusing.
+
+###### Binode types
+       CondExpr,
+
+###### Grammar
+
+       $*exec
+       Expression -> BoolExpr if BoolExpr else BoolExpr ${ {
+                       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
+
+       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;
+
+###### propagate binode cases
+
+       case CondExpr: {
+               /* cond must be Tbool, others must match */
+               struct binode *b2 = cast(binode, b->right);
+               struct type *t2;
+
+               propagate_types(b->left, c, ok, Tbool, 0);
+               t = propagate_types(b2->left, c, ok, type, Rnolabel);
+               t2 = propagate_types(b2->right, c, ok, type ?: t, Rnolabel);
+               return t ?: t2;
+       }
+
+###### interp binode cases
+
+       case CondExpr: {
+               struct binode *b2 = cast(binode, b->right);
+               left = interp_exec(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 from each precedence
-level used by expressions.  The outer most or lowest level precedence
-are Boolean `or` `and`, and `not` which form an `Expression` out of `BTerm`s
-and `BFact`s.
+The next class of expressions to use the `binode` will be Boolean
+expressions.  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
+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,
+       AndThen,
        Or,
        Or,
+       OrElse,
        Not,
 
        Not,
 
-####### Grammar
+###### Grammar
 
        $*exec
 
        $*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;
                } }$
+               | BoolExpr or else BTerm ${ {
+                       struct binode *b = new(binode);
+                       b->op = OrElse;
+                       b->left = $<1;
+                       b->right = $<4;
+                       $0 = b;
+               } }$
                | BTerm ${ $0 = $<1; }$
 
        BTerm -> BTerm and BFact ${ {
                | BTerm ${ $0 = $<1; }$
 
        BTerm -> BTerm and BFact ${ {
@@ -1560,6 +1975,13 @@ and `BFact`s.
                        b->right = $<3;
                        $0 = b;
                } }$
                        b->right = $<3;
                        $0 = b;
                } }$
+               | BTerm and then BFact ${ {
+                       struct binode *b = new(binode);
+                       b->op = AndThen;
+                       b->left = $<1;
+                       b->right = $<4;
+                       $0 = b;
+               } }$
                | BFact ${ $0 = $<1; }$
 
        BFact -> not BFact ${ {
                | BFact ${ $0 = $<1; }$
 
        BFact -> not BFact ${ {
@@ -1576,11 +1998,21 @@ and `BFact`s.
                printf(" and ");
                print_exec(b->right, -1, 0);
                break;
                printf(" and ");
                print_exec(b->right, -1, 0);
                break;
+       case AndThen:
+               print_exec(b->left, -1, 0);
+               printf(" and then ");
+               print_exec(b->right, -1, 0);
+               break;
        case Or:
                print_exec(b->left, -1, 0);
                printf(" or ");
                print_exec(b->right, -1, 0);
                break;
        case Or:
                print_exec(b->left, -1, 0);
                printf(" or ");
                print_exec(b->right, -1, 0);
                break;
+       case OrElse:
+               print_exec(b->left, -1, 0);
+               printf(" or else ");
+               print_exec(b->right, -1, 0);
+               break;
        case Not:
                printf("not ");
                print_exec(b->right, -1, 0);
        case Not:
                printf("not ");
                print_exec(b->right, -1, 0);
@@ -1588,17 +2020,19 @@ and `BFact`s.
 
 ###### propagate binode cases
        case And:
 
 ###### propagate binode cases
        case And:
+       case AndThen:
        case Or:
        case Or:
+       case OrElse:
        case Not:
        case Not:
-               /* both must be Vbool, result is Vbool */
-               propagate_types(b->left, c, ok, Vbool, 0);
-               propagate_types(b->right, c, ok, Vbool, 0);
-               if (type != Vbool && type != Vunknown) {
+               /* both must be Tbool, result is Tbool */
+               propagate_types(b->left, c, ok, Tbool, 0);
+               propagate_types(b->right, c, ok, Tbool, 0);
+               if (type && type != Tbool) {
                        type_err(c, "error: %1 operation found where %2 expected", prog,
                        type_err(c, "error: %1 operation found where %2 expected", prog,
-                                  Vbool, 0, type);
+                                  Tbool, 0, type);
                        *ok = 0;
                }
                        *ok = 0;
                }
-               return Vbool;
+               return Tbool;
 
 ###### interp binode cases
        case And:
 
 ###### interp binode cases
        case And:
@@ -1606,11 +2040,21 @@ and `BFact`s.
                right = interp_exec(b->right);
                rv.bool = rv.bool && right.bool;
                break;
                right = interp_exec(b->right);
                rv.bool = rv.bool && right.bool;
                break;
+       case AndThen:
+               rv = interp_exec(b->left);
+               if (rv.bool)
+                       rv = interp_exec(b->right);
+               break;
        case Or:
                rv = interp_exec(b->left);
                right = interp_exec(b->right);
                rv.bool = rv.bool || right.bool;
                break;
        case Or:
                rv = interp_exec(b->left);
                right = interp_exec(b->right);
                rv.bool = rv.bool || right.bool;
                break;
+       case OrElse:
+               rv = interp_exec(b->left);
+               if (!rv.bool)
+                       rv = interp_exec(b->right);
+               break;
        case Not:
                rv = interp_exec(b->right);
                rv.bool = !rv.bool;
        case Not:
                rv = interp_exec(b->right);
                rv.bool = !rv.bool;
@@ -1694,21 +2138,21 @@ expression operator.
        case GtrEq:
        case Eql:
        case NEql:
        case GtrEq:
        case Eql:
        case NEql:
-               /* Both must match but not labels, result is Vbool */
-               t = propagate_types(b->left, c, ok, Vunknown, Rnolabel);
-               if (t != Vunknown)
+               /* Both must match but not be labels, result is Tbool */
+               t = propagate_types(b->left, c, ok, NULL, Rnolabel);
+               if (t)
                        propagate_types(b->right, c, ok, t, 0);
                else {
                        propagate_types(b->right, c, ok, t, 0);
                else {
-                       t = propagate_types(b->right, c, ok, Vunknown, Rnolabel);
-                       if (t != Vunknown)
+                       t = propagate_types(b->right, c, ok, NULL, Rnolabel);
+                       if (t)
                                t = propagate_types(b->left, c, ok, t, 0);
                }
                                t = propagate_types(b->left, c, ok, t, 0);
                }
-               if (!vtype_compat(type, Vbool, 0)) {
+               if (!type_compat(type, Tbool, 0)) {
                        type_err(c, "error: Comparison returns %1 but %2 expected", prog,
                        type_err(c, "error: Comparison returns %1 but %2 expected", prog,
-                                   Vbool, rules, type);
+                                   Tbool, rules, type);
                        *ok = 0;
                }
                        *ok = 0;
                }
-               return Vbool;
+               return Tbool;
 
 ###### interp binode cases
        case Less:
 
 ###### interp binode cases
        case Less:
@@ -1722,7 +2166,7 @@ expression operator.
                left = interp_exec(b->left);
                right = interp_exec(b->right);
                cmp = value_cmp(left, right);
                left = interp_exec(b->left);
                right = interp_exec(b->right);
                cmp = value_cmp(left, right);
-               rv.vtype = Vbool;
+               rv.type = Tbool;
                switch (b->op) {
                case Less:      rv.bool = cmp <  0; break;
                case LessEq:    rv.bool = cmp <= 0; break;
                switch (b->op) {
                case Less:      rv.bool = cmp <  0; break;
                case LessEq:    rv.bool = cmp <= 0; break;
@@ -1746,12 +2190,12 @@ are included.
 absolute value and negation).  These have different operator names.
 
 We also have a 'Bracket' operator which records where parentheses were
 absolute value and negation).  These have different operator names.
 
 We also have a 'Bracket' operator which records where parentheses were
-found.  This make it easy to reproduce these when printing.  Once
+found.  This makes it easy to reproduce these when printing.  Once
 precedence is handled better I might be able to discard this.
 
 ###### Binode types
        Plus, Minus,
 precedence is handled better I might be able to discard this.
 
 ###### Binode types
        Plus, Minus,
-       Times, Divide,
+       Times, Divide, Rem,
        Concat,
        Absolute, Negate,
        Bracket,
        Concat,
        Absolute, Negate,
        Bracket,
@@ -1801,6 +2245,7 @@ precedence is handled better I might be able to discard this.
 
        Top ->    * ${ $0.op = Times; }$
                | / ${ $0.op = Divide; }$
 
        Top ->    * ${ $0.op = Times; }$
                | / ${ $0.op = Divide; }$
+               | % ${ $0.op = Rem; }$
                | ++ ${ $0.op = Concat; }$
 
 ###### print binode cases
                | ++ ${ $0.op = Concat; }$
 
 ###### print binode cases
@@ -1809,13 +2254,15 @@ precedence is handled better I might be able to discard this.
        case Times:
        case Divide:
        case Concat:
        case Times:
        case Divide:
        case Concat:
+       case Rem:
                print_exec(b->left, indent, 0);
                switch(b->op) {
                print_exec(b->left, indent, 0);
                switch(b->op) {
-               case Plus:   printf(" + "); break;
-               case Minus:  printf(" - "); break;
-               case Times:  printf(" * "); break;
-               case Divide: printf(" / "); break;
-               case Concat: printf(" ++ "); break;
+               case Plus:   fputs(" + ", stdout); break;
+               case Minus:  fputs(" - ", stdout); break;
+               case Times:  fputs(" * ", stdout); break;
+               case Divide: fputs(" / ", stdout); break;
+               case Rem:    fputs(" % ", stdout); break;
+               case Concat: fputs(" ++ ", stdout); break;
                default: abort();
                }
                print_exec(b->right, indent, 0);
                default: abort();
                }
                print_exec(b->right, indent, 0);
@@ -1838,31 +2285,32 @@ precedence is handled better I might be able to discard this.
        case Plus:
        case Minus:
        case Times:
        case Plus:
        case Minus:
        case Times:
+       case Rem:
        case Divide:
        case Divide:
-               /* both must be numbers, result is Vnum */
+               /* both must be numbers, result is Tnum */
        case Absolute:
        case Negate:
                /* as propagate_types ignores a NULL,
                 * unary ops fit here too */
        case Absolute:
        case Negate:
                /* as propagate_types ignores a NULL,
                 * unary ops fit here too */
-               propagate_types(b->left, c, ok, Vnum, 0);
-               propagate_types(b->right, c, ok, Vnum, 0);
-               if (!vtype_compat(type, Vnum, 0)) {
+               propagate_types(b->left, c, ok, Tnum, 0);
+               propagate_types(b->right, c, ok, Tnum, 0);
+               if (!type_compat(type, Tnum, 0)) {
                        type_err(c, "error: Arithmetic returns %1 but %2 expected", prog,
                        type_err(c, "error: Arithmetic returns %1 but %2 expected", prog,
-                                  Vnum, rules, type);
+                                  Tnum, rules, type);
                        *ok = 0;
                }
                        *ok = 0;
                }
-               return Vnum;
+               return Tnum;
 
        case Concat:
 
        case Concat:
-               /* both must be Vstr, result is Vstr */
-               propagate_types(b->left, c, ok, Vstr, 0);
-               propagate_types(b->right, c, ok, Vstr, 0);
-               if (!vtype_compat(type, Vstr, 0)) {
+               /* both must be Tstr, result is Tstr */
+               propagate_types(b->left, c, ok, Tstr, 0);
+               propagate_types(b->right, c, ok, Tstr, 0);
+               if (!type_compat(type, Tstr, 0)) {
                        type_err(c, "error: Concat returns %1 but %2 expected", prog,
                        type_err(c, "error: Concat returns %1 but %2 expected", prog,
-                                  Vstr, rules, type);
+                                  Tstr, rules, type);
                        *ok = 0;
                }
                        *ok = 0;
                }
-               return Vstr;
+               return Tstr;
 
        case Bracket:
                return propagate_types(b->right, c, ok, type, 0);
 
        case Bracket:
                return propagate_types(b->right, c, ok, type, 0);
@@ -1889,6 +2337,20 @@ precedence is handled better I might be able to discard this.
                right = interp_exec(b->right);
                mpq_div(rv.num, rv.num, right.num);
                break;
                right = interp_exec(b->right);
                mpq_div(rv.num, rv.num, right.num);
                break;
+       case Rem: {
+               mpz_t l, r, rem;
+
+               left = interp_exec(b->left);
+               right = interp_exec(b->right);
+               mpz_init(l); mpz_init(r); mpz_init(rem);
+               mpz_tdiv_q(l, mpq_numref(left.num), mpq_denref(left.num));
+               mpz_tdiv_q(r, mpq_numref(right.num), mpq_denref(right.num));
+               mpz_tdiv_r(rem, l, r);
+               rv = val_init(Tnum);
+               mpq_set_z(rv.num, rem);
+               mpz_clear(r); mpz_clear(l); mpz_clear(rem);
+               break;
+       }
        case Negate:
                rv = interp_exec(b->right);
                mpq_neg(rv.num, rv.num);
        case Negate:
                rv = interp_exec(b->right);
                mpq_neg(rv.num, rv.num);
@@ -1903,10 +2365,24 @@ precedence is handled better I might be able to discard this.
        case Concat:
                left = interp_exec(b->left);
                right = interp_exec(b->right);
        case Concat:
                left = interp_exec(b->left);
                right = interp_exec(b->right);
-               rv.vtype = Vstr;
+               rv.type = Tstr;
                rv.str = text_join(left.str, right.str);
                break;
 
                rv.str = text_join(left.str, right.str);
                break;
 
+
+###### value functions
+
+       static struct text text_join(struct text a, struct text b)
+       {
+               struct text rv;
+               rv.len = a.len + b.len;
+               rv.txt = malloc(rv.len);
+               memcpy(rv.txt, a.txt, a.len);
+               memcpy(rv.txt+a.len, b.txt, b.len);
+               return rv;
+       }
+
+
 ### Blocks, Statements, and Statement lists.
 
 Now that we have expressions out of the way we need to turn to
 ### Blocks, Statements, and Statement lists.
 
 Now that we have expressions out of the way we need to turn to
@@ -1957,7 +2433,8 @@ and a list.  So we need a function to re-order a list.
 
 The only stand-alone statement we introduce at this stage is `pass`
 which does nothing and is represented as a `NULL` pointer in a `Block`
 
 The only stand-alone statement we introduce at this stage is `pass`
 which does nothing and is represented as a `NULL` pointer in a `Block`
-list.
+list.  Other stand-alone statements will follow once the infrastructure
+is in-place.
 
 ###### Binode types
        Block,
 
 ###### Binode types
        Block,
@@ -2049,21 +2526,21 @@ list.
 ###### propagate binode cases
        case Block:
        {
 ###### propagate binode cases
        case Block:
        {
-               /* If any statement returns something other then Vnone
-                * or Vbool then all such must return same type.
-                * As each statement may be Vnone or something else,
-                * we must always pass Vunknown down, otherwise an incorrect
-                * error might occur.  We never return Vnone unless it is
+               /* If any statement returns something other than Tnone
+                * or Tbool then all such must return same type.
+                * As each statement may be Tnone or something else,
+                * we must always pass NULL (unknown) down, otherwise an incorrect
+                * error might occur.  We never return Tnone unless it is
                 * passed in.
                 */
                struct binode *e;
 
                for (e = b; e; e = cast(binode, e->right)) {
                 * passed in.
                 */
                struct binode *e;
 
                for (e = b; e; e = cast(binode, e->right)) {
-                       t = propagate_types(e->left, c, ok, Vunknown, rules);
-                       if ((rules & Rboolok) && t == Vbool)
-                               t = Vunknown;
-                       if (t != Vunknown && t != Vnone && t != Vbool) {
-                               if (type == Vunknown)
+                       t = propagate_types(e->left, c, ok, NULL, rules);
+                       if ((rules & Rboolok) && t == Tbool)
+                               t = NULL;
+                       if (t && t != Tnone && t != Tbool) {
+                               if (!type)
                                        type = t;
                                else if (t != type) {
                                        type_err(c, "error: expected %1%r, found %2",
                                        type = t;
                                else if (t != type) {
                                        type_err(c, "error: expected %1%r, found %2",
@@ -2077,7 +2554,7 @@ list.
 
 ###### interp binode cases
        case Block:
 
 ###### interp binode cases
        case Block:
-               while (rv.vtype == Vnone &&
+               while (rv.type == Tnone &&
                       b) {
                        if (b->left)
                                rv = interp_exec(b->left);
                       b) {
                        if (b->left)
                                rv = interp_exec(b->left);
@@ -2152,8 +2629,8 @@ same solution.
 
        case Print:
                /* don't care but all must be consistent */
 
        case Print:
                /* don't care but all must be consistent */
-               propagate_types(b->left, c, ok, Vunknown, Rnolabel);
-               propagate_types(b->right, c, ok, Vunknown, Rnolabel);
+               propagate_types(b->left, c, ok, NULL, Rnolabel);
+               propagate_types(b->right, c, ok, NULL, Rnolabel);
                break;
 
 ###### interp binode cases
                break;
 
 ###### interp binode cases
@@ -2173,7 +2650,7 @@ same solution.
                                        sep = ' ';
                        } else if (sep)
                                eol = 0;
                                        sep = ' ';
                        } else if (sep)
                                eol = 0;
-               left.vtype = Vnone;
+               left.type = Tnone;
                if (eol)
                        printf("\n");
                break;
                if (eol)
                        printf("\n");
                break;
@@ -2187,29 +2664,36 @@ will be correct so the interpreter just needs to perform the
 calculation.  There is a form of assignment which declares a new
 variable as well as assigning a value.  If a name is assigned before
 it is declared, and error will be raised as the name is created as
 calculation.  There is a form of assignment which declares a new
 variable as well as assigning a value.  If a name is assigned before
 it is declared, and error will be raised as the name is created as
-`Vlabel` and it is illegal to assign to such names.
+`Tlabel` and it is illegal to assign to such names.
 
 ###### Binode types
        Assign,
        Declare,
 
 ###### SimpleStatement Grammar
 
 ###### Binode types
        Assign,
        Declare,
 
 ###### SimpleStatement Grammar
-       | Variable = Expression ${ {
-                       struct var *v = cast(var, $1);
-
+       | Variable = Expression ${
                        $0 = new(binode);
                        $0->op = Assign;
                        $0->left = $<1;
                        $0->right = $<3;
                        $0 = new(binode);
                        $0->op = Assign;
                        $0->left = $<1;
                        $0->right = $<3;
-                       if (v->var && !v->var->constant) {
-                               /* FIXME error? */
-                       }
-               } }$
-       | VariableDecl Expression ${
+               }$
+       | VariableDecl = Expression ${
                        $0 = new(binode);
                        $0->op = Declare;
                        $0->left = $<1;
                        $0 = new(binode);
                        $0->op = Declare;
                        $0->left = $<1;
-                       $0->right =$<2;
+                       $0->right =$<3;
+               }$
+
+       | VariableDecl ${
+                       if ($1->var->where_set == NULL) {
+                               type_err(config2context(config), "Variable declared with no type or value: %v",
+                                        $1, NULL, 0, NULL);
+                       } else {
+                               $0 = new(binode);
+                               $0->op = Declare;
+                               $0->left = $<1;
+                               $0->right = NULL;
+                       }
                }$
 
 ###### print binode cases
                }$
 
 ###### print binode cases
@@ -2224,49 +2708,92 @@ it is declared, and error will be raised as the name is created as
                break;
 
        case Declare:
                break;
 
        case Declare:
+               {
+               struct variable *v = cast(var, b->left)->var;
                do_indent(indent, "");
                print_exec(b->left, indent, 0);
                do_indent(indent, "");
                print_exec(b->left, indent, 0);
-               if (cast(var, b->left)->var->constant)
-                       printf(" ::= ");
-               else
-                       printf(" := ");
-               print_exec(b->right, indent, 0);
+               if (cast(var, b->left)->var->constant) {
+                       if (v->where_decl == v->where_set) {
+                               printf("::");
+                               type_print(v->val.type, stdout);
+                               printf(" ");
+                       } else
+                               printf(" ::");
+               } else {
+                       if (v->where_decl == v->where_set) {
+                               printf(":");
+                               type_print(v->val.type, stdout);
+                               printf(" ");
+                       } else
+                               printf(" :");
+               }
+               if (b->right) {
+                       printf("= ");
+                       print_exec(b->right, indent, 0);
+               }
                if (indent >= 0)
                        printf("\n");
                if (indent >= 0)
                        printf("\n");
+               }
                break;
 
 ###### propagate binode cases
 
        case Assign:
        case Declare:
                break;
 
 ###### propagate binode cases
 
        case Assign:
        case Declare:
-               /* Both must match and not be labels, result is Vnone */
-               t = propagate_types(b->left, c, ok, Vunknown, Rnolabel);
-               if (t != Vunknown) {
+               /* Both must match and not be labels,
+                * Type must support 'dup',
+                * For Assign, left must not be constant.
+                * result is Tnone
+                */
+               t = propagate_types(b->left, c, ok, NULL,
+                                   Rnolabel | (b->op == Assign ? Rnoconstant : 0));
+               if (!b->right)
+                       return Tnone;
+
+               if (t) {
                        if (propagate_types(b->right, c, ok, t, 0) != t)
                                if (b->left->type == Xvar)
                                        type_err(c, "info: variable '%v' was set as %1 here.",
                        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, Vnone);
+                                                cast(var, b->left)->var->where_set, t, rules, Tnone);
                } else {
                } else {
-                       t = propagate_types(b->right, c, ok, Vunknown, Rnolabel);
-                       if (t != Vunknown)
-                               propagate_types(b->left, c, ok, t, 0);
+                       t = propagate_types(b->right, c, ok, NULL, Rnolabel);
+                       if (t)
+                               propagate_types(b->left, c, ok, t,
+                                               (b->op == Assign ? Rnoconstant : 0));
                }
                }
-               return Vnone;
+               if (t && t->dup == NULL) {
+                       type_err(c, "error: cannot assign value of type %1", b, t, 0, NULL);
+                       *ok = 0;
+               }
+               return Tnone;
 
                break;
 
 ###### interp binode cases
 
        case Assign:
 
                break;
 
 ###### interp binode cases
 
        case Assign:
+               lleft = linterp_exec(b->left);
+               right = interp_exec(b->right);
+               if (lleft) {
+                       free_value(*lleft);
+                       *lleft = right;
+               } else
+                       free_value(right);
+               right.type = NULL;
+               break;
+
        case Declare:
        {
                struct variable *v = cast(var, b->left)->var;
                if (v->merged)
                        v = v->merged;
        case Declare:
        {
                struct variable *v = cast(var, b->left)->var;
                if (v->merged)
                        v = v->merged;
-               right = interp_exec(b->right);
+               if (b->right)
+                       right = interp_exec(b->right);
+               else
+                       right = val_init(v->val.type);
                free_value(v->val);
                v->val = right;
                free_value(v->val);
                v->val = right;
-               right.vtype = Vunknown;
+               right.type = NULL;
                break;
        }
 
                break;
        }
 
@@ -2677,57 +3204,57 @@ defined.
 ###### propagate exec cases
        case Xcond_statement:
        {
 ###### propagate exec cases
        case Xcond_statement:
        {
-               // forpart and dopart must return Vnone
-               // thenpart must return Vnone if there is a dopart,
+               // forpart and dopart must return Tnone
+               // thenpart must return Tnone if there is a dopart,
                // otherwise it is like elsepart.
                // condpart must:
                // otherwise it is like elsepart.
                // condpart must:
-               //    be bool if there is not casepart
+               //    be bool if there is no casepart
                //    match casepart->values if there is a switchpart
                //    either be bool or match casepart->value if there
                //             is a whilepart
                //    match casepart->values if there is a switchpart
                //    either be bool or match casepart->value if there
                //             is a whilepart
-               // elsepart, casepart->action must match there return type
-               // expected of this statement.
+               // elsepart and casepart->action must match the return type
+               //   expected of this statement.
                struct cond_statement *cs = cast(cond_statement, prog);
                struct casepart *cp;
 
                struct cond_statement *cs = cast(cond_statement, prog);
                struct casepart *cp;
 
-               t = propagate_types(cs->forpart, c, ok, Vnone, 0);
-               if (!vtype_compat(Vnone, t, 0))
+               t = propagate_types(cs->forpart, c, ok, Tnone, 0);
+               if (!type_compat(Tnone, t, 0))
                        *ok = 0;
                        *ok = 0;
-               t = propagate_types(cs->dopart, c, ok, Vnone, 0);
-               if (!vtype_compat(Vnone, t, 0))
+               t = propagate_types(cs->dopart, c, ok, Tnone, 0);
+               if (!type_compat(Tnone, t, 0))
                        *ok = 0;
                if (cs->dopart) {
                        *ok = 0;
                if (cs->dopart) {
-                       t = propagate_types(cs->thenpart, c, ok, Vnone, 0);
-                       if (!vtype_compat(Vnone, t, 0))
+                       t = propagate_types(cs->thenpart, c, ok, Tnone, 0);
+                       if (!type_compat(Tnone, t, 0))
                                *ok = 0;
                }
                if (cs->casepart == NULL)
                                *ok = 0;
                }
                if (cs->casepart == NULL)
-                       propagate_types(cs->condpart, c, ok, Vbool, 0);
+                       propagate_types(cs->condpart, c, ok, Tbool, 0);
                else {
                        /* Condpart must match case values, with bool permitted */
                else {
                        /* Condpart must match case values, with bool permitted */
-                       t = Vunknown;
+                       t = NULL;
                        for (cp = cs->casepart;
                        for (cp = cs->casepart;
-                            cp && (t == Vunknown); cp = cp->next)
-                               t = propagate_types(cp->value, c, ok, Vunknown, 0);
-                       if (t == Vunknown && cs->condpart)
-                               t = propagate_types(cs->condpart, c, ok, Vunknown, Rboolok);
+                            cp && !t; cp = cp->next)
+                               t = propagate_types(cp->value, c, ok, NULL, 0);
+                       if (!t && cs->condpart)
+                               t = propagate_types(cs->condpart, c, ok, NULL, Rboolok);
                        // Now we have a type (I hope) push it down
                        // Now we have a type (I hope) push it down
-                       if (t != Vunknown) {
+                       if (t) {
                                for (cp = cs->casepart; cp; cp = cp->next)
                                        propagate_types(cp->value, c, ok, t, 0);
                                propagate_types(cs->condpart, c, ok, t, Rboolok);
                        }
                }
                // (if)then, else, and case parts must return expected type.
                                for (cp = cs->casepart; cp; cp = cp->next)
                                        propagate_types(cp->value, c, ok, t, 0);
                                propagate_types(cs->condpart, c, ok, t, Rboolok);
                        }
                }
                // (if)then, else, and case parts must return expected type.
-               if (!cs->dopart && type == Vunknown)
-                       type = propagate_types(cs->thenpart, c, ok, Vunknown, rules);
-               if (type == Vunknown)
-                       type = propagate_types(cs->elsepart, c, ok, Vunknown, rules);
+               if (!cs->dopart && !type)
+                       type = propagate_types(cs->thenpart, c, ok, NULL, rules);
+               if (!type)
+                       type = propagate_types(cs->elsepart, c, ok, NULL, rules);
                for (cp = cs->casepart;
                for (cp = cs->casepart;
-                    cp && type == Vunknown;
+                    cp && !type;
                     cp = cp->next)
                     cp = cp->next)
-                       type = propagate_types(cp->action, c, ok, Vunknown, rules);
-               if (type != Vunknown) {
+                       type = propagate_types(cp->action, c, ok, NULL, rules);
+               if (type) {
                        if (!cs->dopart)
                                propagate_types(cs->thenpart, c, ok, type, rules);
                        propagate_types(cs->elsepart, c, ok, type, rules);
                        if (!cs->dopart)
                                propagate_types(cs->thenpart, c, ok, type, rules);
                        propagate_types(cs->elsepart, c, ok, type, rules);
@@ -2735,7 +3262,7 @@ defined.
                                propagate_types(cp->action, c, ok, type, rules);
                        return type;
                } else
                                propagate_types(cp->action, c, ok, type, rules);
                        return type;
                } else
-                       return Vunknown;
+                       return NULL;
        }
 
 ###### interp exec cases
        }
 
 ###### interp exec cases
@@ -2751,19 +3278,19 @@ defined.
                        if (c->condpart)
                                cnd = interp_exec(c->condpart);
                        else
                        if (c->condpart)
                                cnd = interp_exec(c->condpart);
                        else
-                               cnd.vtype = Vnone;
-                       if (!(cnd.vtype == Vnone ||
-                             (cnd.vtype == Vbool && cnd.bool != 0)))
+                               cnd.type = Tnone;
+                       if (!(cnd.type == Tnone ||
+                             (cnd.type == Tbool && cnd.bool != 0)))
                                break;
                                break;
-                       // cnd is Vnone or Vbool, doesn't need to be freed
+                       // cnd is Tnone or Tbool, doesn't need to be freed
                        if (c->dopart)
                                interp_exec(c->dopart);
 
                        if (c->thenpart) {
                        if (c->dopart)
                                interp_exec(c->dopart);
 
                        if (c->thenpart) {
-                               v = interp_exec(c->thenpart);
-                               if (v.vtype != Vnone || !c->dopart)
-                                       return v;
-                               free_value(v);
+                               rv = interp_exec(c->thenpart);
+                               if (rv.type != Tnone || !c->dopart)
+                                       goto Xcond_done;
+                               free_value(rv);
                        }
                } while (c->dopart);
 
                        }
                } while (c->dopart);
 
@@ -2772,15 +3299,229 @@ defined.
                        if (value_cmp(v, cnd) == 0) {
                                free_value(v);
                                free_value(cnd);
                        if (value_cmp(v, cnd) == 0) {
                                free_value(v);
                                free_value(cnd);
-                               return interp_exec(cp->action);
+                               rv = interp_exec(cp->action);
+                               goto Xcond_done;
                        }
                        free_value(v);
                }
                free_value(cnd);
                if (c->elsepart)
                        }
                        free_value(v);
                }
                free_value(cnd);
                if (c->elsepart)
-                       return interp_exec(c->elsepart);
-               v.vtype = Vnone;
-               return v;
+                       rv = interp_exec(c->elsepart);
+               else
+                       rv.type = Tnone;
+       Xcond_done:
+               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;
+
+               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;
+               {
+               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);
+               }
+               }
+       }$
+
+       | [ 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;
+
+###### 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;
        }
 
 ### Finally the whole program.
        }
 
 ### Finally the whole program.
@@ -2812,7 +3553,7 @@ analysis is a bit more interesting at this level.
                }$
                | ERROR ${
                        tok_err(config2context(config),
                }$
                | ERROR ${
                        tok_err(config2context(config),
-                               "error: unhandled parse error.", &$1);
+                               "error: unhandled parse error", &$1);
                }$
 
        Varlist -> Varlist ArgDecl ${
                }$
 
        Varlist -> Varlist ArgDecl ${
@@ -2862,28 +3603,28 @@ analysis is a bit more interesting at this level.
                        return 0;
                do {
                        ok = 1;
                        return 0;
                do {
                        ok = 1;
-                       propagate_types(b->right, c, &ok, Vnone, 0);
+                       propagate_types(b->right, c, &ok, Tnone, 0);
                } while (ok == 2);
                if (!ok)
                        return 0;
 
                for (b = cast(binode, b->left); b; b = cast(binode, b->right)) {
                        struct var *v = cast(var, b->left);
                } while (ok == 2);
                if (!ok)
                        return 0;
 
                for (b = cast(binode, b->left); b; b = cast(binode, b->right)) {
                        struct var *v = cast(var, b->left);
-                       if (v->var->val.vtype == Vunknown) {
+                       if (!v->var->val.type) {
                                v->var->where_set = b;
                                v->var->where_set = b;
-                               val_init(&v->var->val, Vstr);
+                               v->var->val = val_prepare(Tstr);
                        }
                }
                b = cast(binode, prog);
                do {
                        ok = 1;
                        }
                }
                b = cast(binode, prog);
                do {
                        ok = 1;
-                       propagate_types(b->right, c, &ok, Vnone, 0);
+                       propagate_types(b->right, c, &ok, Tnone, 0);
                } while (ok == 2);
                if (!ok)
                        return 0;
 
                /* Make sure everything is still consistent */
                } while (ok == 2);
                if (!ok)
                        return 0;
 
                /* Make sure everything is still consistent */
-               propagate_types(b->right, c, &ok, Vnone, 0);
+               propagate_types(b->right, c, &ok, Tnone, 0);
                return !!ok;
        }
 
                return !!ok;
        }
 
@@ -2906,7 +3647,8 @@ analysis is a bit more interesting at this level.
                        }
                        al = cast(binode, al->right);
                        free_value(*vl);
                        }
                        al = cast(binode, al->right);
                        free_value(*vl);
-                       if (!parse_value(vl, argv[0]))
+                       *vl = parse_value(vl->type, argv[0]);
+                       if (vl->type == NULL)
                                exit(1);
                        argv++;
                }
                                exit(1);
                        argv++;
                }
@@ -2945,15 +3687,17 @@ Fibonacci, and performs a binary search for a number.
                /* If a variable is not used after the 'if', no
                 * merge happens, so types can be different
                 */
                /* If a variable is not used after the 'if', no
                 * merge happens, so types can be different
                 */
-               if A * 2 > B:
-                       double := "yes"
+               if A > B * 2:
+                       double:string = "yes"
                        print A, "is more than twice", B, "?", double
                else:
                        print A, "is more than twice", B, "?", double
                else:
-                       double := A*2
-                       print "double", A, "is only", double
+                       double := B*2
+                       print "double", B, "is", double
 
 
-               a := A; b := B
-               if a > 0 and b > 0:
+               a : number
+               a = A;
+               b:number = B
+               if a > 0 and then b > 0:
                        while a != b:
                                if a < b:
                                        b = b - a
                        while a != b:
                                if a < b:
                                        b = b - a
@@ -2997,3 +3741,24 @@ Fibonacci, and performs a binary search for a number.
                        print "Yay, I found", target
                case GiveUp:
                        print "Closest I found was", mid
                        print "Yay, I found", target
                case GiveUp:
                        print "Closest I found was", mid
+
+               size::=55
+               list:[size]number
+               list[0] = 1234
+               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:"
+               for i:=0; then i = i + 1; while i < size:
+                       print "list[",i,"]=",list[i]
+
+               for i := 1; then i=i+1; while i < size:
+                       for j:=i-1; then j=j-1; while j >= 0:
+                               if list[j] > list[j+1]:
+                                       t:= list[j]
+                                       list[j] = list[j+1]
+                                       list[j+1] = t
+               print "After sort:"
+               for i:=0; then i = i + 1; while i < size:
+                       print "list[",i,"]=",list[i]