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
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);
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.
###### 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;
};
};
- 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;
}
}
- 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
val->vtype = type;
switch(type) {
case Vnone:abort();
- case Vnolabel:
case Vunknown: break;
case Vnum:
mpq_init(val->num); break;
rv.vtype = v.vtype;
switch (rv.vtype) {
case Vnone:
- case Vnolabel:
case Vunknown: break;
case Vlabel:
rv.label = v.label;
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;
case Vunknown:
printf("*Unknown*"); break;
case Vnone:
- case Vnolabel:
printf("*no-value*"); break;
case Vlabel:
printf("*label-%p*", v.label); break;
char tail[3] = "";
switch(vl->vtype) {
- case Vnolabel:
case Vlabel:
case Vunknown:
case Vnone:
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
###### 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;
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;
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 ::= ${ {
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);
}
} }$
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;
}
/* 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;
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;
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;
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;
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;
}
}
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
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;
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.
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;