aboutsummaryrefslogtreecommitdiff
path: root/src/check.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/check.c')
-rw-r--r--src/check.c35
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))