]> ocean-lang.org Git - ocean/blobdiff - csrc/oceani.mdc
oceani: detect and report tails on numbers and strings.
[ocean] / csrc / oceani.mdc
index 460e75ed103b1648c0c3947432cec801be07a1fa..ebb69ade3d2a32f40e0e3666ed6416cf8d2cc4e9 100644 (file)
@@ -109,6 +109,8 @@ option.
        ## ast
        struct parse_context {
                struct token_config config;
+               char *file_name;
+               int parse_error;
                ## parse context
        };
 
@@ -197,6 +199,7 @@ option.
                        fprintf(stderr, "oceani: cannot open %s\n", argv[optind]);
                        exit(1);
                }
+               context.file_name = argv[optind];
                len = lseek(fd, 0, 2);
                file = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
                s = code_extract(file, file+len, NULL);
@@ -224,11 +227,15 @@ option.
                } else
                        prog = parse_oceani(s->code, &context.config,
                                    dotrace ? stderr : NULL);
+               if (!prog) {
+                       fprintf(stderr, "oceani: fatal parser error.\n");
+                       context.parse_error = 1;
+               }
                if (prog && doprint)
                        print_exec(*prog, 0, brackets);
-               if (prog && doexec) {
+               if (prog && doexec && !context.parse_error) {
                        if (!analyse_prog(*prog, &context)) {
-                               fprintf(stderr, "oceani: type error in program\n");
+                               fprintf(stderr, "oceani: type error in program - not running.\n");
                                exit(1);
                        }
                        interp_prog(*prog, argv+optind+1);
@@ -244,7 +251,7 @@ option.
                        s = t;
                }
                ## free context
-               exit(0);
+               exit(context.parse_error ? 1 : 0);
        }
 
 ### Analysis
@@ -290,10 +297,90 @@ These names are given a type of "label" and a unique value.
 This allows them to fill the role of a name in an enumerated type, which
 is useful for testing the `switch` statement.
 
+As we will see, the condition part of a `while` statement can return
+either a Boolean or some other type.  This requires that the expect
+type that gets passed around comprises a type (`enum vtype`) and a
+flag to indicate that `Vbool` is also permitted.
+
 As there are, as yet, no distinct types that are compatible, there
-isn't much subtlety in the analysis.  When we hav distinct number
+isn't much subtlety in the analysis.  When we have distinct number
 types, this will become more interesting.
 
+#### Error reporting
+
+When analysis discovers an inconsistency it needs to report an error;
+just refusing to run the code esure that the error doesn't cascade,
+but by itself it isn't very useful.  A clear understand of the sort of
+error message that are useful will help guide the process of analysis.
+
+At a simplistic level, the only sort of error that type analysis can
+report is that the type of some construct doesn't match a contextual
+requirement.  For example, in `4 + "hello"` the addition provides a
+contextual requirement for numbers, but `"hello"` is not a number.  In
+this particular example no further information is needed as the types
+are obvious from local information.  When a variable is involved that
+isn't the case.  It may be helpful to explain why the variable has a
+particular type, by indicating the location where the type was set,
+whether by declaration or usage.
+
+Using a recursive-descent analysis we can easily detect a problem at
+multiple locations. In "`hello:= "there"; 4 + hello`" the addition
+will detect that one argument is not a number and the usage of `hello`
+will detect that a number was wanted, but not provided.  In this
+(early) version of the language, we will generate error reports at
+multiple locations, to the use of `hello` will report an error and
+explain were the value was set, and the addition will report an error
+and say why numbers are needed.  To be able to report locations for
+errors, each language element will need to record a file location
+(line and column) and each variable will need to record the language
+element where its type was set.  For now we will assume that each line
+of an error message indicates one location in the file, and up to 2
+types.  So we provide a `printf`-like function which takes a format, a
+language (a `struct exec` which has not yet been introduced), and 2
+types. "`$1`" reports the first type, "`$2`" reports the second.  We
+will need a function to print the location, once we know how that is
+stored.
+
+###### forward decls
+
+       static void fput_loc(struct exec *loc, FILE *f);
+
+###### core functions
+
+       static void type_err(struct parse_context *c,
+                            char *fmt, struct exec *loc,
+                            enum vtype t1, enum vtype t2)
+       {
+               fprintf(stderr, "%s:", c->file_name);
+               fput_loc(loc, stderr);
+               for (; *fmt ; fmt++) {
+                       if (*fmt != '%') {
+                               fputc(*fmt, stderr);
+                               continue;
+                       }
+                       fmt++;
+                       switch (*fmt) {
+                       case '%': fputc(*fmt, stderr); break;
+                       default: fputc('?', stderr); break;
+                       case '1':
+                               fputs(vtype_names[t1], stderr);
+                               break;
+                       case '2':
+                               fputs(vtype_names[t2], stderr);
+                               break;
+                       ## format cases
+                       }
+               }
+               fputs("\n", stderr);
+               c->parse_error = 1;
+       }
+
+       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);
+               c->parse_error = 1;
+       }
+
 ## Data Structures
 
 One last introductory step before detailing the language elements and
@@ -350,6 +437,9 @@ to parse each type from a string.
                char tail[2];
        };
 
+       char *vtype_names[] = {"nolabel", "unknown", "none", "string",
+                              "number", "Boolean", "label"};
+
 ###### ast functions
        static void free_value(struct value v)
        {
@@ -364,8 +454,10 @@ to parse each type from a string.
                }
        }
 
-       static int vtype_compat(enum vtype require, enum vtype have)
+       static int vtype_compat(enum vtype require, enum vtype have, int bool_permitted)
        {
+               if (bool_permitted && have == Vbool)
+                       return 1;
                switch (require) {
                case Vnolabel:
                        return have != Vlabel;
@@ -577,6 +669,8 @@ cannot nest, so a declaration while a name is in-scope is an error.
                struct variable *previous;
                struct value val;
                struct binding *name;
+               struct exec *where_decl;// where name was declared
+               struct exec *where_set; // where type was set
                ## variable fields
        };
 
@@ -808,7 +902,7 @@ all pending-scope variables become conditionally scoped.
 
                switch (v ? v->scope : OutScope) {
                case InScope:
-                       /* Signal error ... once I build error signalling support */
+                       /* Caller will report the error */
                        return NULL;
                case CondScope:
                        for (;
@@ -953,9 +1047,16 @@ subclasses, and to access these we need to be able to `cast` the
                if (__mptr && *__mptr != X##structname) abort();                \
                (struct structname *)( (char *)__mptr);})
 
-       #define new(structname) ({                      \
+       #define new(structname) ({                                              \
                struct structname *__ptr = ((struct structname *)calloc(1,sizeof(struct structname))); \
-               __ptr->type = X##structname;            \
+               __ptr->type = X##structname;                                            \
+               __ptr->line = -1; __ptr->column = -1;                                   \
+               __ptr;})
+
+       #define new_pos(structname, token) ({                                           \
+               struct structname *__ptr = ((struct structname *)calloc(1,sizeof(struct structname))); \
+               __ptr->type = X##structname;                                            \
+               __ptr->line = token.line; __ptr->column = token.col;                    \
                __ptr;})
 
 ###### ast
@@ -965,6 +1066,7 @@ subclasses, and to access these we need to be able to `cast` the
        };
        struct exec {
                enum exec_types type;
+               int line, column;
        };
        struct binode {
                struct exec;
@@ -974,6 +1076,25 @@ subclasses, and to access these we need to be able to `cast` the
                struct exec *left, *right;
        };
 
+###### ast functions
+
+       static int __fput_loc(struct exec *loc, FILE *f)
+       {
+               if (loc->line >= 0) {
+                       fprintf(f, "%d:%d: ", loc->line, loc->column);
+                       return 1;
+               }
+               if (loc->type == Xbinode)
+                       return __fput_loc(cast(binode,loc)->left, f) ||
+                              __fput_loc(cast(binode,loc)->right, f);
+               return 0;
+       }
+       static void fput_loc(struct exec *loc, FILE *f)
+       {
+               if (!__fput_loc(loc, f))
+                       fprintf(f, "??:??: ");
+       }
+
 Each different type of `exec` node needs a number of functions
 defined, a bit like methods.  We must be able to be able to free it,
 print it, analyse it and execute it.  Once we have specific `exec`
@@ -1041,6 +1162,8 @@ also want to know what sort of bracketing to use.
 
        static void print_exec(struct exec *e, int indent, int bracket)
        {
+               if (!e)
+                       return;
                switch (e->type) {
                case Xbinode:
                        print_binode(cast(binode, e), indent, bracket); break;
@@ -1057,16 +1180,17 @@ also want to know what sort of bracketing to use.
 As discussed, analysis involves propagating type requirements around
 the program and looking for errors.
 
-So `propagate_types` is passed a type 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 `vtype`
+together with a `bool_permitted` flag) 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.
 
 ###### core functions
 
-       static enum vtype propagate_types(struct exec *prog, enum vtype type,
-                                         int *ok)
+       static enum vtype propagate_types(struct exec *prog, struct parse_context *c, int *ok,
+                                         enum vtype type, int bool_permitted)
        {
                enum vtype t;
 
@@ -1149,30 +1273,39 @@ an executable.
 
        $*val
        Value ->  True ${
-                       $0 = new(val);
+                       $0 = new_pos(val, $1);
                        $0->val.vtype = Vbool;
                        $0->val.bool = 1;
                        }$
                | False ${
-                       $0 = new(val);
+                       $0 = new_pos(val, $1);
                        $0->val.vtype = Vbool;
                        $0->val.bool = 0;
                        }$
                | NUMBER ${
-                       $0 = new(val);
+                       $0 = new_pos(val, $1);
                        $0->val.vtype = Vnum;
                        if (number_parse($0->val.num, $0->val.tail, $1.txt) == 0)
                                mpq_init($0->val.num);
+                               if ($0->val.tail[0])
+                                       tok_err(config2context(config), "error: unsupported number suffix.",
+                                               &$1);
                        }$
                | STRING ${
-                       $0 = new(val);
+                       $0 = new_pos(val, $1);
                        $0->val.vtype = Vstr;
                        string_parse(&$1, '\\', &$0->val.str, $0->val.tail);
+                       if ($0->val.tail[0])
+                               tok_err(config2context(config), "error: unsupported string suffix.",
+                                       &$1);
                        }$
                | MULTI_STRING ${
-                       $0 = new(val);
+                       $0 = new_pos(val, $1);
                        $0->val.vtype = Vstr;
                        string_parse(&$1, '\\', &$0->val.str, $0->val.tail);
+                       if ($0->val.tail[0])
+                               tok_err(config2context(config), "error: unsupported string suffix.",
+                                       &$1);
                        }$
 
 ###### print exec cases
@@ -1191,8 +1324,11 @@ an executable.
                case Xval:
                {
                        struct val *val = cast(val, prog);
-                       if (!vtype_compat(type, val->val.vtype))
+                       if (!vtype_compat(type, val->val.vtype, bool_permitted)) {
+                               type_err(c, "error: expected %1 found %2",
+                                          prog, type, val->val.vtype);
                                *ok = 0;
+                       }
                        return val->val.vtype;
                }
 
@@ -1256,25 +1392,47 @@ link to find the primary instance.
        $*var
        VariableDecl -> IDENTIFIER := ${ {
                struct variable *v = var_decl(config2context(config), $1.txt);
-               $0 = new(var);
+               $0 = new_pos(var, $1);
                $0->var = v;
+               if (v)
+                       v->where_decl = $0;
+               else {
+                       v = var_ref(config2context(config), $1.txt);
+                       $0->var = v;
+                       type_err(config2context(config), "error: variable '%v' redeclared",
+                                $0, Vnone, Vnone);
+                       type_err(config2context(config), "info: this is where '%v' was first declared",
+                                v->where_decl, Vnone, Vnone);
+               }
        } }$
            | IDENTIFIER ::= ${ {
                struct variable *v = var_decl(config2context(config), $1.txt);
-               v->constant = 1;
-               $0 = new(var);
+               $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, Vnone, Vnone);
+                       type_err(config2context(config), "info: this is where '%v' was first declared",
+                                v->where_decl, Vnone, Vnone);
+               }
        } }$
 
        Variable -> IDENTIFIER ${ {
                struct variable *v = var_ref(config2context(config), $1.txt);
+               $0 = new_pos(var, $1);
                if (v == NULL) {
                        /* This might be a label - allocate a var just in case */
                        v = var_decl(config2context(config), $1.txt);
-                       if (v)
+                       if (v) {
                                val_init(&v->val, Vlabel);
+                               v->where_set = $0;
+                       }
                }
-               $0 = new(var);
                $0->var = v;
        } }$
 
@@ -1289,6 +1447,19 @@ link to find the primary instance.
                break;
        }
 
+###### format cases
+       case 'v':
+               if (loc->type == Xvar) {
+                       struct var *v = cast(var, loc);
+                       if (v->var) {
+                               struct binding *b = v->var->name;
+                               fprintf(stderr, "%.*s", b->name.len, b->name.txt);
+                       } else
+                               fputs("???", stderr);
+               } else
+                       fputs("NOTVAR", stderr);
+               break;
+
 ###### propagate exec cases
 
        case Xvar:
@@ -1296,6 +1467,7 @@ link to find the primary instance.
                struct var *var = cast(var, prog);
                struct variable *v = var->var;
                if (!v) {
+                       type_err(c, "%d:BUG: no variable!!", prog, Vnone, Vnone);
                        *ok = 0;
                        return Vnone;
                }
@@ -1304,12 +1476,18 @@ link to find the primary instance.
                if (v->val.vtype == Vunknown) {
                        if (type > Vunknown && *ok != 0) {
                                val_init(&v->val, type);
+                               v->where_set = prog;
                                *ok = 2;
                        }
                        return type;
                }
-               if (!vtype_compat(type, v->val.vtype))
+               if (!vtype_compat(type, v->val.vtype, bool_permitted)) {
+                       type_err(c, "error: expected %1 but variable '%v' is %2", prog,
+                                type, v->val.vtype);
+                       type_err(c, "info: this is where '%v' was set to %1", v->where_set,
+                                v->val.vtype, Vnone);
                        *ok = 0;
+               }
                if (type <= Vunknown)
                        return v->val.vtype;
                return type;
@@ -1352,28 +1530,31 @@ and `BFact`s.
 
 ####### Grammar
 
-       $*binode
-       Expression -> Expression or BTerm ${
-                       $0 = new(binode);
-                       $0->op = Or;
-                       $0->left = $<1;
-                       $0->right = $<3;
-               }$
+       $*exec
+       Expression -> Expression or BTerm ${ {
+                       struct binode *b = new(binode);
+                       b->op = Or;
+                       b->left = $<1;
+                       b->right = $<3;
+                       $0 = b;
+               } }$
                | BTerm ${ $0 = $<1; }$
 
-       BTerm -> BTerm and BFact ${
-                       $0 = new(binode);
-                       $0->op = And;
-                       $0->left = $<1;
-                       $0->right = $<3;
-               }$
+       BTerm -> BTerm and BFact ${ {
+                       struct binode *b = new(binode);
+                       b->op = And;
+                       b->left = $<1;
+                       b->right = $<3;
+                       $0 = b;
+               } }$
                | BFact ${ $0 = $<1; }$
 
-       BFact -> not BFact ${
-                       $0 = new(binode);
-                       $0->op = Not;
-                       $0->right = $<2;
-                       }$
+       BFact -> not BFact ${ {
+                       struct binode *b = new(binode);
+                       b->op = Not;
+                       b->right = $<2;
+                       $0 = b;
+               } }$
                ## other BFact
 
 ###### print binode cases
@@ -1397,10 +1578,13 @@ and `BFact`s.
        case Or:
        case Not:
                /* both must be Vbool, result is Vbool */
-               propagate_types(b->left, Vbool, ok);
-               propagate_types(b->right, Vbool, ok);
-               if (type != Vbool && type > Vunknown)
+               propagate_types(b->left, c, ok, Vbool, 0);
+               propagate_types(b->right, c, ok, Vbool, 0);
+               if (type != Vbool && type > Vunknown) {
+                       type_err(c, "error: %1 operation found where %2 expected", prog,
+                                  Vbool, type);
                        *ok = 0;
+               }
                return Vbool;
 
 ###### interp binode cases
@@ -1450,12 +1634,13 @@ expression operator.
        NEql,
 
 ###### other BFact
-       | Expr CMPop Expr ${
-                       $0 = new(binode);
-                       $0->op = $2.op;
-                       $0->left = $<1;
-                       $0->right = $<3;
-               }$
+       | Expr CMPop Expr ${ {
+                       struct binode *b = new(binode);
+                       b->op = $2.op;
+                       b->left = $<1;
+                       b->right = $<3;
+                       $0 = b;
+       } }$
        | Expr ${ $0 = $<1; }$
 
 ###### Grammar
@@ -1497,16 +1682,19 @@ expression operator.
        case Eql:
        case NEql:
                /* Both must match but not labels, result is Vbool */
-               t = propagate_types(b->left, Vnolabel, ok);
+               t = propagate_types(b->left, c, ok, Vnolabel, 0);
                if (t > Vunknown)
-                       propagate_types(b->right, t, ok);
+                       propagate_types(b->right, c, ok, t, 0);
                else {
-                       t = propagate_types(b->right, Vnolabel, ok);
+                       t = propagate_types(b->right, c, ok, Vnolabel, 0);
                        if (t > Vunknown)
-                               t = propagate_types(b->left, t, ok);
+                               t = propagate_types(b->left, c, ok, t, 0);
                }
-               if (!vtype_compat(type, Vbool))
+               if (!vtype_compat(type, Vbool, 0)) {
+                       type_err(c, "error: Comparison returns %1 but %2 expected", prog,
+                                   Vbool, type);
                        *ok = 0;
+               }
                return Vbool;
 
 ###### interp binode cases
@@ -1557,35 +1745,39 @@ precedence is handled better I might be able to discard this.
 
 ###### Grammar
 
-       $*binode
-       Expr -> Expr Eop Term ${
-                       $0 = new(binode);
-                       $0->op = $2.op;
-                       $0->left = $<1;
-                       $0->right = $<3;
-               }$
+       $*exec
+       Expr -> Expr Eop Term ${ {
+                       struct binode *b = new(binode);
+                       b->op = $2.op;
+                       b->left = $<1;
+                       b->right = $<3;
+                       $0 = b;
+               } }$
                | Term ${ $0 = $<1; }$
 
-       Term -> Term Top Factor ${
-                       $0 = new(binode);
-                       $0->op = $2.op;
-                       $0->left = $<1;
-                       $0->right = $<3;
-               }$
+       Term -> Term Top Factor ${ {
+                       struct binode *b = new(binode);
+                       b->op = $2.op;
+                       b->left = $<1;
+                       b->right = $<3;
+                       $0 = b;
+               } }$
                | Factor ${ $0 = $<1; }$
 
-       Factor -> ( Expression ) ${
-                       $0 = new(binode);
-                       $0->op = Bracket;
-                       $0->right = $<2;
-               }$
-               | Uop Factor ${
-                       $0 = new(binode);
-                       $0->op = $1.op;
-                       $0->right = $<2;
-               }$
-               | Value ${ $0 = (struct binode *)$<1; }$
-               | Variable ${ $0 = (struct binode *)$<1; }$
+       Factor -> ( Expression ) ${ {
+                       struct binode *b = new_pos(binode, $1);
+                       b->op = Bracket;
+                       b->right = $<2;
+                       $0 = b;
+               } }$
+               | Uop Factor ${ {
+                       struct binode *b = new(binode);
+                       b->op = $1.op;
+                       b->right = $<2;
+                       $0 = b;
+               } }$
+               | Value ${ $0 = $<1; }$
+               | Variable ${ $0 = $<1; }$
 
        $eop
        Eop ->    + ${ $0.op = Plus; }$
@@ -1639,22 +1831,28 @@ precedence is handled better I might be able to discard this.
        case Negate:
                /* as propagate_types ignores a NULL,
                 * unary ops fit here too */
-               propagate_types(b->left, Vnum, ok);
-               propagate_types(b->right, Vnum, ok);
-               if (!vtype_compat(type, Vnum))
+               propagate_types(b->left, c, ok, Vnum, 0);
+               propagate_types(b->right, c, ok, Vnum, 0);
+               if (!vtype_compat(type, Vnum, 0)) {
+                       type_err(c, "error: Arithmetic returns %1 but %2 expected", prog,
+                                  Vnum, type);
                        *ok = 0;
+               }
                return Vnum;
 
        case Concat:
                /* both must be Vstr, result is Vstr */
-               propagate_types(b->left, Vstr, ok);
-               propagate_types(b->right, Vstr, ok);
-               if (!vtype_compat(type, Vstr))
+               propagate_types(b->left, c, ok, Vstr, 0);
+               propagate_types(b->right, c, ok, Vstr, 0);
+               if (!vtype_compat(type, Vstr, 0)) {
+                       type_err(c, "error: Concat returns %1 but %2 expected", prog,
+                                  Vstr, type);
                        *ok = 0;
+               }
                return Vstr;
 
        case Bracket:
-               return propagate_types(b->right, type, ok);
+               return propagate_types(b->right, c, ok, type, 0);
 
 ###### interp binode cases
 
@@ -1839,20 +2037,26 @@ list.
        case Block:
        {
                /* If any statement returns something other then Vnone
-                * then all such must return same type.
+                * 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.
+                * error might occur.  We never return Vnone unless it is
+                * passed in.
                 */
                struct binode *e;
 
                for (e = b; e; e = cast(binode, e->right)) {
-                       t = propagate_types(e->left, Vunknown, ok);
-                       if (t != Vunknown && t != Vnone) {
+                       t = propagate_types(e->left, c, ok, Vunknown, bool_permitted);
+                       if (bool_permitted && t == Vbool)
+                               t = Vunknown;
+                       if (t != Vunknown && t != Vnone && t != Vbool) {
                                if (type == Vunknown)
                                        type = t;
-                               else if (t != type)
+                               else if (t != type) {
+                                       type_err(c, "error: expected %1, found %2",
+                                                e->left, type, t);
                                        *ok = 0;
+                               }
                        }
                }
                return type;
@@ -1935,8 +2139,8 @@ same solution.
 
        case Print:
                /* don't care but all must be consistent */
-               propagate_types(b->left, Vnolabel, ok);
-               propagate_types(b->right, Vnolabel, ok);
+               propagate_types(b->left, c, ok, Vnolabel, 0);
+               propagate_types(b->right, c, ok, Vnolabel, 0);
                break;
 
 ###### interp binode cases
@@ -2023,13 +2227,16 @@ it is declared, and error will be raised as the name is created as
        case Assign:
        case Declare:
                /* Both must match and not be labels, result is Vnone */
-               t = propagate_types(b->left, Vnolabel, ok);
-               if (t > Vunknown)
-                       propagate_types(b->right, t, ok);
-               else {
-                       t = propagate_types(b->right, Vnolabel, ok);
+               t = propagate_types(b->left, c, ok, Vnolabel, 0);
+               if (t > Vunknown) {
+                       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, Vnone);
+               } else {
+                       t = propagate_types(b->right, c, ok, Vnolabel, 0);
                        if (t > Vunknown)
-                               t = propagate_types(b->left, t, ok);
+                               propagate_types(b->left, c, ok, t, 0);
                }
                return Vnone;
 
@@ -2062,7 +2269,7 @@ function.
 
 ###### SimpleStatement Grammar
        | use Expression ${
-               $0 = new(binode);
+               $0 = new_pos(binode, $1);
                $0->op = Use;
                $0->right = $<2;
        }$
@@ -2080,7 +2287,7 @@ function.
 
        case Use:
                /* result matches value */
-               return propagate_types(b->right, type, ok);
+               return propagate_types(b->right, c, ok, type, 0);
 
 ###### interp binode cases
 
@@ -2090,16 +2297,28 @@ function.
 
 ### The Conditional Statement
 
-This is the biggy and currently the only complex statement.
-This subsumes `if`, `while`, `do/while`, `switch`, and some parts of
-`for`.  It is comprised of a number of parts, all of which are
-optional though set combinations apply.
+This is the biggy and currently the only complex statement.  This
+subsumes `if`, `while`, `do/while`, `switch`, and some parts of `for`.
+It is comprised of a number of parts, all of which are optional though
+set combinations apply.  Each part is (usually) a key word (`then` is
+sometimes optional) followed by either an expression of a code block,
+except the `casepart` which is a "key word and an expression" followed
+by a code block.  The code-block option is valid for all parts and,
+where an expression is also allowed, the code block can use the `use`
+statement to report a value.  If the code block does no report a value
+the effect is similar to reporting `False`.
+
+The `else` and `case` parts, as well as `then` when combined with
+`if`, can contain a `use` statement which will apply to some
+containing conditional statement. `for` parts, `do` parts and `then`
+parts used with `for` can never contain a `use`, except in some
+subordinate conditional statement.
 
 If there is a `forpart`, it is executed first, only once.
 If there is a `dopart`, then it is executed repeatedly providing
 always that the `condpart` or `cond`, if present, does not return a non-True
 value.  `condpart` can fail to return any value if it simply executes
-to completion.  This is treated the same as returning True.
+to completion.  This is treated the same as returning `True`.
 
 If there is a `thenpart` it will be executed whenever the `condpart`
 or `cond` returns True (or does not return any value), but this will happen
@@ -2126,6 +2345,17 @@ condition following "`while`" is in a scope the covers the body
 extension.  Code following "`then`" (both looping and non-looping),
 "`else`" and "`case`" each get their own local scope.
 
+The type requirements on the code block in a `whilepart` are quite
+unusal.  It is allowed to return a value of some identifiable type, in
+which case the loop abort and an appropriate `casepart` is run, or it
+can return a Boolean, in which case the loop either continues to the
+`dopart` (on `True`) or aborts and runs the `elsepart` (on `False`).
+This is different both from the `ifpart` code block which is expected to
+return a Boolean, or the `switchpart` code block which is expected to
+return the same type as the casepart values.  The correct analysis of
+the type of the `whilepart` code block is the reason for the
+`bool_permitted` flag which is passed to `propagate_types()`.
+
 The `cond_statement` cannot fit into a `binode` so a new `exec` is
 defined.
 
@@ -2435,47 +2665,61 @@ defined.
        case Xcond_statement:
        {
                // forpart and dopart must return Vnone
-               // condpart must be bool or match casepart->values
-               // thenpart, elsepart, casepart->action must match
-               // or be Vnone
+               // thenpart must return Vnone if there is a dopart,
+               // otherwise it is like elsepart.
+               // condpart must:
+               //    be bool if there is not casepart
+               //    match casepart->values if there is a switchpart
+               //    either be bool or match casepart->value if there
+               //             is a whilepart
+               // elsepart, casepart->action must match there return type
+               // expected of this statement.
                struct cond_statement *cs = cast(cond_statement, prog);
-               struct casepart *c;
+               struct casepart *cp;
 
-               t = propagate_types(cs->forpart, Vnone, ok);
-               if (!vtype_compat(Vnone, t))
+               t = propagate_types(cs->forpart, c, ok, Vnone, 0);
+               if (!vtype_compat(Vnone, t, 0))
                        *ok = 0;
-               t = propagate_types(cs->dopart, Vnone, ok);
-               if (!vtype_compat(Vnone, t))
+               t = propagate_types(cs->dopart, c, ok, Vnone, 0);
+               if (!vtype_compat(Vnone, t, 0))
                        *ok = 0;
+               if (cs->dopart) {
+                       t = propagate_types(cs->thenpart, c, ok, Vnone, 0);
+                       if (!vtype_compat(Vnone, t, 0))
+                               *ok = 0;
+               }
                if (cs->casepart == NULL)
-                       propagate_types(cs->condpart, Vbool, ok);
+                       propagate_types(cs->condpart, c, ok, Vbool, 0);
                else {
+                       /* Condpart must match case values, with bool permitted */
                        t = Vunknown;
-                       for (c = cs->casepart;
-                            c && (t == Vunknown); c = c->next)
-                               t = propagate_types(c->value, Vunknown, ok);
+                       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, Vunknown, ok);
+                               t = propagate_types(cs->condpart, c, ok, Vunknown, 1);
                        // Now we have a type (I hope) push it down
                        if (t != Vunknown) {
-                               for (c = cs->casepart; c; c = c->next)
-                                       propagate_types(c->value, t, ok);
-                               propagate_types(cs->condpart, t, ok);
+                               for (cp = cs->casepart; cp; cp = cp->next)
+                                       propagate_types(cp->value, c, ok, t, 0);
+                               propagate_types(cs->condpart, c, ok, t, 1);
                        }
                }
-               if (type == Vunknown || type == Vnone)
-                       type = propagate_types(cs->thenpart, Vunknown, ok);
-               if (type == Vunknown || type == Vnone)
-                       type = propagate_types(cs->elsepart, Vunknown, ok);
-               for (c = cs->casepart;
-                    c && (type == Vunknown || type == Vnone);
-                    c = c->next)
-                       type = propagate_types(c->action, Vunknown, ok);
-               if (type != Vunknown && type != Vnone) {
-                       propagate_types(cs->thenpart, type, ok);
-                       propagate_types(cs->elsepart, type, ok);
-                       for (c = cs->casepart; c ; c = c->next)
-                               propagate_types(c->action, type, ok);
+               // (if)then, else, and case parts must return expected type.
+               if (!cs->dopart && type == Vunknown)
+                       type = propagate_types(cs->thenpart, c, ok, Vunknown, bool_permitted);
+               if (type == Vunknown)
+                       type = propagate_types(cs->elsepart, c, ok, Vunknown, bool_permitted);
+               for (cp = cs->casepart;
+                    cp && type == Vunknown;
+                    cp = cp->next)
+                       type = propagate_types(cp->action, c, ok, Vunknown, bool_permitted);
+               if (type > Vunknown) {
+                       if (!cs->dopart)
+                               propagate_types(cs->thenpart, c, ok, type, bool_permitted);
+                       propagate_types(cs->elsepart, c, ok, type, bool_permitted);
+                       for (cp = cs->casepart; cp ; cp = cp->next)
+                               propagate_types(cp->action, c, ok, type, bool_permitted);
                        return type;
                } else
                        return Vunknown;
@@ -2551,7 +2795,11 @@ analysis is a bit more interesting at this level.
                $0->right = $<4;
                var_block_close(config2context(config), CloseSequential);
                if (config2context(config)->scope_stack) abort();
-       }$
+               }$
+               | ERROR ${
+                       tok_err(config2context(config),
+                               "error: unhandled parse error.", &$1);
+               }$
 
        Varlist -> Varlist ArgDecl ${
                        $0 = new(binode);
@@ -2596,37 +2844,44 @@ analysis is a bit more interesting at this level.
                struct binode *b = cast(binode, prog);
                int ok = 1;
 
+               if (!b)
+                       return 0;
                do {
                        ok = 1;
-                       propagate_types(b->right, Vnone, &ok);
+                       propagate_types(b->right, c, &ok, Vnone, 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);
-                       if (v->var->val.vtype == Vunknown)
+                       if (v->var->val.vtype == Vunknown) {
+                               v->var->where_set = b;
                                val_init(&v->var->val, Vstr);
+                       }
                }
                b = cast(binode, prog);
                do {
                        ok = 1;
-                       propagate_types(b->right, Vnone, &ok);
+                       propagate_types(b->right, c, &ok, Vnone, 0);
                } while (ok == 2);
                if (!ok)
                        return 0;
 
                /* Make sure everything is still consistent */
-               propagate_types(b->right, Vnone, &ok);
+               propagate_types(b->right, c, &ok, Vnone, 0);
                return !!ok;
        }
 
        static void interp_prog(struct exec *prog, char **argv)
        {
                struct binode *p = cast(binode, prog);
-               struct binode *al = cast(binode, p->left);
+               struct binode *al;
                struct value v;
 
+               if (!prog)
+                       return;
+               al = cast(binode, p->left);
                while (al) {
                        struct var *v = cast(var, al->left);
                        struct value *vl = &v->var->val;
@@ -2698,7 +2953,7 @@ Fibonacci, and performs a binary search for a number.
 
                for:
                        togo := 10
-                       f1 := 1; f2 := 1;
+                       f1 := 1; f2 := 1
                        print "Fibonacci:", f1,f2,
                then togo = togo - 1
                while togo > 0:
@@ -2722,7 +2977,7 @@ Fibonacci, and performs a binary search for a number.
                                hi = mid
                        if hi - lo < 1:
                                use GiveUp
-
+                       use True
                do: pass
                case Found:
                        print "Yay, I found", target