From 470b2955e36125aa99ef54d9aa54db026172819f Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 20 Apr 2019 14:40:52 +1000 Subject: [PATCH] oceani: discard Vnolabel infavour of rules. Instead Vnolabel as a type, change "bool_permitted" to a set of rules (Rboolok) and add a new rule: Rnolabel. This requires changes to type_err() and elsewhere. Signed-off-by: NeilBrown --- csrc/oceani.mdc | 150 +++++++++++++++++++++++------------------------- 1 file changed, 73 insertions(+), 77 deletions(-) diff --git a/csrc/oceani.mdc b/csrc/oceani.mdc index a8da9fb..4f39775 100644 --- a/csrc/oceani.mdc +++ b/csrc/oceani.mdc @@ -339,7 +339,9 @@ 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. +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. ###### forward decls @@ -349,7 +351,7 @@ stored. static void type_err(struct parse_context *c, char *fmt, struct exec *loc, - enum vtype t1, enum vtype t2) + enum vtype t1, int rules, enum vtype t2) { fprintf(stderr, "%s:", c->file_name); fput_loc(loc, stderr); @@ -397,24 +399,18 @@ which hold the values. 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` -which can be anything and `Vnolabel` which can be anything except a -label). +(`Vnone`) and where we don't know what type to expect yet (`Vunknown`). 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 -with anything, and `Vnolabel` is compatible with anything except a -label. A separate function to encode this rule will simplify some code -later. - -There is an extra complication that this function needs to handle, -which is described later when the Conditional Statement is introduced. -In certain cases where a particular type is generally expected, a -Boolean is also alway permitted. To handle those cases, we explicitly tell -`vtype_compat()` is a Boolean is permitted. +with anything. There are two special cases with type compatibility, +both related to the Conditional Statement which will be described +later. In some cases a Boolean can be accepted as well as some other +primary type, and in others any type is acceptable except a label (`Vlabel`). +A separate function encode these cases will simplify some code later. When assigning command line arguments to variables, we need to be able to parse each type from a string. @@ -430,7 +426,7 @@ to parse each type from a string. ###### ast struct value { - enum vtype {Vnolabel, Vunknown, Vnone, Vstr, Vnum, Vbool, Vlabel} vtype; + enum vtype {Vunknown, Vnone, Vstr, Vnum, Vbool, Vlabel} vtype; union { struct text str; mpq_t num; @@ -439,15 +435,22 @@ to parse each type from a string. }; }; - char *vtype_names[] = {"nolabel", "unknown", "none", "string", + enum val_rules {Rnolabel = 1<<0, Rboolok = 1<<1}; + + char *vtype_names[] = {"unknown", "none", "string", "number", "Boolean", "label"}; +###### format cases + case 'r': + if (rules & Rnolabel) + fputs(" (labels not permitted)", stderr); + break; + ###### ast functions static void free_value(struct value v) { switch (v.vtype) { case Vnone: - case Vnolabel: case Vunknown: break; case Vstr: free(v.str.txt); break; case Vnum: mpq_clear(v.num); break; @@ -456,18 +459,16 @@ to parse each type from a string. } } - static int vtype_compat(enum vtype require, enum vtype have, int bool_permitted) + static int vtype_compat(enum vtype require, enum vtype have, int rules) { - if (bool_permitted && have == Vbool) + if ((rules & Rboolok) && have == Vbool) return 1; - switch (require) { - case Vnolabel: - return have != Vlabel; - case Vunknown: + if ((rules & Rnolabel) && have == Vlabel) + return 0; + if (require == Vunknown || have == Vunknown) return 1; - default: - return have == Vunknown || require == have; - } + + return require == have; } ###### value functions @@ -477,7 +478,6 @@ to parse each type from a string. val->vtype = type; switch(type) { case Vnone:abort(); - case Vnolabel: case Vunknown: break; case Vnum: mpq_init(val->num); break; @@ -500,7 +500,6 @@ to parse each type from a string. rv.vtype = v.vtype; switch (rv.vtype) { case Vnone: - case Vnolabel: case Vunknown: break; case Vlabel: rv.label = v.label; @@ -532,7 +531,6 @@ to parse each type from a string. case Vstr: cmp = text_cmp(left.str, right.str); break; case Vbool: cmp = left.bool - right.bool; break; case Vnone: - case Vnolabel: case Vunknown: cmp = 0; } return cmp; @@ -554,7 +552,6 @@ to parse each type from a string. case Vunknown: printf("*Unknown*"); break; case Vnone: - case Vnolabel: printf("*no-value*"); break; case Vlabel: printf("*label-%p*", v.label); break; @@ -581,7 +578,6 @@ to parse each type from a string. char tail[3] = ""; switch(vl->vtype) { - case Vnolabel: case Vlabel: case Vunknown: case Vnone: @@ -1189,7 +1185,7 @@ 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 a `bool_permitted` flag) that the `exec` is expected to +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 @@ -1198,7 +1194,7 @@ remains unchanged at `1`, then no more propagation is needed. ###### core functions static enum vtype propagate_types(struct exec *prog, struct parse_context *c, int *ok, - enum vtype type, int bool_permitted) + enum vtype type, int rules) { enum vtype t; @@ -1341,9 +1337,9 @@ an executable. case Xval: { struct val *val = cast(val, prog); - if (!vtype_compat(type, val->val.vtype, bool_permitted)) { - type_err(c, "error: expected %1 found %2", - prog, type, val->val.vtype); + if (!vtype_compat(type, val->val.vtype, rules)) { + type_err(c, "error: expected %1%r found %2", + prog, type, rules, val->val.vtype); *ok = 0; } return val->val.vtype; @@ -1417,9 +1413,9 @@ link to find the primary instance. v = var_ref(config2context(config), $1.txt); $0->var = v; type_err(config2context(config), "error: variable '%v' redeclared", - $0, Vnone, Vnone); + $0, Vnone, 0, Vnone); type_err(config2context(config), "info: this is where '%v' was first declared", - v->where_decl, Vnone, Vnone); + v->where_decl, Vnone, 0, Vnone); } } }$ | IDENTIFIER ::= ${ { @@ -1433,9 +1429,9 @@ link to find the primary instance. v = var_ref(config2context(config), $1.txt); $0->var = v; type_err(config2context(config), "error: variable '%v' redeclared", - $0, Vnone, Vnone); + $0, Vnone, 0, Vnone); type_err(config2context(config), "info: this is where '%v' was first declared", - v->where_decl, Vnone, Vnone); + v->where_decl, Vnone, 0, Vnone); } } }$ @@ -1484,28 +1480,28 @@ 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); + type_err(c, "%d:BUG: no variable!!", prog, Vnone, 0, Vnone); *ok = 0; return Vnone; } if (v->merged) v = v->merged; if (v->val.vtype == Vunknown) { - if (type > Vunknown && *ok != 0) { + 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, bool_permitted)) { - type_err(c, "error: expected %1 but variable '%v' is %2", prog, - type, v->val.vtype); + if (!vtype_compat(type, v->val.vtype, rules)) { + type_err(c, "error: expected %1%r but variable '%v' is %2", prog, + type, rules, v->val.vtype); type_err(c, "info: this is where '%v' was set to %1", v->where_set, - v->val.vtype, Vnone); + v->val.vtype, rules, Vnone); *ok = 0; } - if (type <= Vunknown) + if (type == Vunknown) return v->val.vtype; return type; } @@ -1597,9 +1593,9 @@ and `BFact`s. /* 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) { + if (type != Vbool && type != Vunknown) { type_err(c, "error: %1 operation found where %2 expected", prog, - Vbool, type); + Vbool, 0, type); *ok = 0; } return Vbool; @@ -1699,17 +1695,17 @@ expression operator. case Eql: case NEql: /* Both must match but not labels, result is Vbool */ - t = propagate_types(b->left, c, ok, Vnolabel, 0); - if (t > Vunknown) + t = propagate_types(b->left, c, ok, Vunknown, Rnolabel); + if (t != Vunknown) propagate_types(b->right, c, ok, t, 0); else { - t = propagate_types(b->right, c, ok, Vnolabel, 0); - if (t > Vunknown) + t = propagate_types(b->right, c, ok, Vunknown, Rnolabel); + if (t != Vunknown) t = propagate_types(b->left, c, ok, t, 0); } if (!vtype_compat(type, Vbool, 0)) { type_err(c, "error: Comparison returns %1 but %2 expected", prog, - Vbool, type); + Vbool, rules, type); *ok = 0; } return Vbool; @@ -1852,7 +1848,7 @@ precedence is handled better I might be able to discard this. 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); + Vnum, rules, type); *ok = 0; } return Vnum; @@ -1863,7 +1859,7 @@ precedence is handled better I might be able to discard this. 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); + Vstr, rules, type); *ok = 0; } return Vstr; @@ -2063,15 +2059,15 @@ list. struct binode *e; for (e = b; e; e = cast(binode, e->right)) { - t = propagate_types(e->left, c, ok, Vunknown, bool_permitted); - if (bool_permitted && t == Vbool) + 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) type = t; else if (t != type) { - type_err(c, "error: expected %1, found %2", - e->left, type, t); + type_err(c, "error: expected %1%r, found %2", + e->left, type, rules, t); *ok = 0; } } @@ -2156,8 +2152,8 @@ same solution. case Print: /* don't care but all must be consistent */ - propagate_types(b->left, c, ok, Vnolabel, 0); - propagate_types(b->right, c, ok, Vnolabel, 0); + propagate_types(b->left, c, ok, Vunknown, Rnolabel); + propagate_types(b->right, c, ok, Vunknown, Rnolabel); break; ###### interp binode cases @@ -2244,15 +2240,15 @@ 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, c, ok, Vnolabel, 0); - if (t > Vunknown) { + t = propagate_types(b->left, c, ok, Vunknown, Rnolabel); + 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); + cast(var, b->left)->var->where_set, t, rules, Vnone); } else { - t = propagate_types(b->right, c, ok, Vnolabel, 0); - if (t > Vunknown) + t = propagate_types(b->right, c, ok, Vunknown, Rnolabel); + if (t != Vunknown) propagate_types(b->left, c, ok, t, 0); } return Vnone; @@ -2371,7 +2367,7 @@ 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()`. +`Rboolok` flag which is passed to `propagate_types()`. The `cond_statement` cannot fit into a `binode` so a new `exec` is defined. @@ -2714,29 +2710,29 @@ defined. 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, 1); + t = propagate_types(cs->condpart, c, ok, Vunknown, Rboolok); // Now we have a type (I hope) push it down if (t != Vunknown) { for (cp = cs->casepart; cp; cp = cp->next) propagate_types(cp->value, c, ok, t, 0); - propagate_types(cs->condpart, c, ok, t, 1); + 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, bool_permitted); + type = propagate_types(cs->thenpart, c, ok, Vunknown, rules); if (type == Vunknown) - type = propagate_types(cs->elsepart, c, ok, Vunknown, bool_permitted); + type = propagate_types(cs->elsepart, c, ok, Vunknown, rules); for (cp = cs->casepart; cp && type == Vunknown; cp = cp->next) - type = propagate_types(cp->action, c, ok, Vunknown, bool_permitted); - if (type > Vunknown) { + type = propagate_types(cp->action, c, ok, Vunknown, rules); + if (type != Vunknown) { if (!cs->dopart) - propagate_types(cs->thenpart, c, ok, type, bool_permitted); - propagate_types(cs->elsepart, c, ok, type, bool_permitted); + propagate_types(cs->thenpart, c, ok, type, rules); + propagate_types(cs->elsepart, c, ok, type, rules); for (cp = cs->casepart; cp ; cp = cp->next) - propagate_types(cp->action, c, ok, type, bool_permitted); + propagate_types(cp->action, c, ok, type, rules); return type; } else return Vunknown; -- 2.43.0