diff options
Diffstat (limited to 'src/check.c')
-rw-r--r-- | src/check.c | 35 |
1 files changed, 27 insertions, 8 deletions
diff --git a/src/check.c b/src/check.c index 880f71f..b4ea2b1 100644 --- a/src/check.c +++ b/src/check.c @@ -1,8 +1,10 @@ #include <posthaste/check.h> #include <posthaste/debug.h> +#include <posthaste/utils.h> -#define UNUSED(x) (void)x - +/* state required to implement semantic checking. In this case it turned out + * that I only needed a pointer to the function/procedure the checker is + * currently in, mainly for return type matching. */ struct state { struct ast *parent; }; @@ -21,6 +23,7 @@ int analyze_visibility(struct scope *scope, struct ast *n) return -1; break; + default: } @@ -46,6 +49,9 @@ static int analyze_list(struct state *state, struct scope *scope, struct ast *l) static struct ast *file_scope_find_analyzed(struct state *state, struct scope *scope, char *id) { + /* look up a definition (func/proc/variable) in this scope and possible + * parent scopes up to and including the file scope, and make sure that + * whatever we find has been analyzed to completeness. */ struct ast *exists = file_scope_find(scope, id); if (!exists) return NULL; @@ -87,7 +93,7 @@ static int analyze_func_def(struct state *state, struct scope *scope, if (analyze_type(scope, f)) return -1; - /* slightly hacky, but allows recursion */ + /* slightly hacky, but easiest way to allow recursion */ ast_set_flags(f, AST_FLAG_CHECKED); struct scope *func_scope = create_scope(); @@ -119,7 +125,6 @@ static int analyze_proc_def(struct state *state, struct scope *scope, if (analyze_type(scope, p)) return -1; - /* slightly hacky, but allows recursion */ ast_set_flags(p, AST_FLAG_CHECKED); struct scope *proc_scope = create_scope(); @@ -137,8 +142,11 @@ static int analyze_proc_def(struct state *state, struct scope *scope, return -1; struct ast *last = ast_last(proc_body(p)); + /* here we could for example implement checking that all branches in an unless + * return, but for simplicity require user to add explicit return as + * last statement in body */ if (p->t != TYPE_VOID && last->k != AST_RETURN) { - semantic_error(scope, p, "can't prove that proc returns"); + semantic_error(scope, p, "can't prove that proc reaches return statement"); return -1; } @@ -522,10 +530,8 @@ static int analyze_attr(struct state *state, struct scope *scope, struct ast *d) if (same_id(d->id, "year")) return 0; - if (same_id(d->id, "weekday")) { - d->t = TYPE_STRING; + if (same_id(d->id, "weekday")) return 0; - } if (same_id(d->id, "weeknum")) return 0; @@ -788,9 +794,13 @@ static int analyze(struct state *state, struct scope *scope, struct ast *n) case AST_UNLESS: ret = analyze_unless(state, scope, n); break; case AST_UNLESS_EXPR: ret = analyze_unless_expr(state, scope, n); break; default: break; + /* not all nodes are in this switch statement, as the internal + * ones are assumed to be correct */ } if (ret == 0) { + /* even though sometimes we might not need the type, this is a + * fairly effective sanity check */ assert(has_type(n)); } @@ -798,13 +808,22 @@ static int analyze(struct state *state, struct scope *scope, struct ast *n) return ret; } +/* this I guess would be classified as a two-phase semantic checker, where the + * first phase checks that there aren't any multiply defined + * procs/funcs/variables, and the second phase does (mainly) type checking. I didn't + * explicitly call it type checking as it technically also checks date + * attributes, date +- etc. which were fairly easy to implement in the same + * phase. */ int check(struct scope *scope, struct ast *root) { + /* first add all procedures/functions to the top level scope so we can + * find them later */ foreach_node(n, root) { if (analyze_visibility(scope, n)) return -1; } + /* actually analyze all nodes */ foreach_node(n, root) { struct state state = {0}; if (analyze(&state, scope, n)) |