#include "gravity_symboltable.h"
#include "gravity_semacheck2.h"
#include "gravity_compiler.h"
#include "gravity_visitor.h"
struct semacheck_t {
gnode_r *declarations;
uint16_r statements;
uint32_t lasterror;
};
typedef struct semacheck_t semacheck_t;
#define REPORT_ERROR(node,...) report_error(self, GRAVITY_ERROR_SEMANTIC, (gnode_t *)node, __VA_ARGS__);
#define REPORT_WARNING(node,...) report_error(self, GRAVITY_WARNING, (gnode_t *)node, __VA_ARGS__)
#define STATEMENTS ((semacheck_t *)self->data)->statements
#define PUSH_STATEMENT(stat) marray_push(uint16_t, STATEMENTS, (uint16_t)stat)
#define POP_STATEMENT() marray_pop(STATEMENTS)
#define TOP_STATEMENT() (marray_size(STATEMENTS) ? marray_last(STATEMENTS) : 0)
#define TOP_STATEMENT_ISA(stat) (TOP_STATEMENT() == stat)
#define TOP_STATEMENT_ISA_SWITCH() (TOP_STATEMENT_ISA(TOK_KEY_SWITCH))
#define TOP_STATEMENT_ISA_LOOP() ((TOP_STATEMENT_ISA(TOK_KEY_WHILE)) || \
(TOP_STATEMENT_ISA(TOK_KEY_REPEAT)) || \
(TOP_STATEMENT_ISA(TOK_KEY_FOR)))
#define PUSH_DECLARATION(node) gnode_array_push(((semacheck_t *)self->data)->declarations, (gnode_t*)node)
#define POP_DECLARATION() gnode_array_pop(((semacheck_t *)self->data)->declarations)
#define TOP_DECLARATION() gnode_array_get(((semacheck_t *)self->data)->declarations, gnode_array_size(((semacheck_t *)self->data)->declarations)-1)
#define ISA(n1,_tag) ((n1) ? (((gnode_t *)n1)->tag == _tag) : 0)
#define ISA_CLASS(n1) (((gnode_t *)n1)->tag == NODE_CLASS_DECL)
#define ISA_VAR_DECLARATION(n1) (((gnode_t *)n1)->tag == NODE_VARIABLE_DECL)
#define ISA_VARIABLE(n1) (((gnode_t *)n1)->tag == NODE_VARIABLE)
#define ISA_LITERAL(n1) (((gnode_t *)n1)->tag == NODE_LITERAL_EXPR)
#define ISA_IDENTIFIER(n1) (((gnode_t *)n1)->tag == NODE_IDENTIFIER_EXPR)
#define ISA_ID(n1) (((gnode_t *)n1)->tag == NODE_ID_EXPR)
#define SET_LOCAL_INDEX(var, symtable) var->index = (uint16_t)symboltable_local_index(symtable)
#define SET_NODE_LOCATION(_node, _type, _idx, _nup) _node->location.type = _type; _node->location.index = _idx; _node->location.nup = _nup;
static void report_error (gvisitor_t *self, error_type_t error_type, gnode_t *node, const char *format, ...) {
semacheck_t *current = (semacheck_t *)self->data;
if (node->token.lineno == current->lasterror) return;
current->lasterror = node->token.lineno;
++self->nerr;
void *data = (self->delegate) ? ((gravity_delegate_t *)self->delegate)->xdata : NULL;
gravity_error_callback error_fn = (self->delegate) ? ((gravity_delegate_t *)self->delegate)->error_callback : NULL;
char buffer[1024];
va_list arg;
if (format) {
va_start (arg, format);
vsnprintf(buffer, sizeof(buffer), format, arg);
va_end (arg);
}
error_desc_t error_desc = {
.code = 0,
.lineno = node->token.lineno,
.colno = node->token.colno,
.fileid = node->token.fileid,
.offset = node->token.position
};
if (error_fn) error_fn(error_type, buffer, error_desc, data);
else printf("%s\n", buffer);
}
static symboltable_t *symtable_from_node (gnode_t *node) {
if (ISA(node, NODE_LIST_STAT)) return ((gnode_compound_stmt_t *)node)->symtable;
if (ISA(node, NODE_CLASS_DECL)) return ((gnode_class_decl_t *)node)->symtable;
if (ISA(node, NODE_ENUM_DECL)) return ((gnode_enum_decl_t *)node)->symtable;
if (ISA(node, NODE_MODULE_DECL)) return ((gnode_module_decl_t *)node)->symtable;
if (ISA(node, NODE_FUNCTION_DECL)) return ((gnode_function_decl_t *)node)->symtable;
assert(0);
return NULL;
}
static gnode_t *lookup_node (gnode_t *node, const char *identifier) {
symboltable_t *symtable = symtable_from_node(node);
return symboltable_lookup(symtable, identifier);
}
static gnode_t *lookup_identifier (gvisitor_t *self, const char *identifier, gnode_identifier_expr_t *node) {
gnode_r *decls = ((semacheck_t *)self->data)->declarations;
size_t len = gnode_array_size(decls);
if (len == 0) return NULL;
uint16_t nf = 0;
uint16_t nc = 0;
gnode_t *base_node = gnode_array_get(decls, len-1);
bool base_is_class = ISA(base_node, NODE_CLASS_DECL);
for (int i=(int)len-1; i>=0; --i) {
gnode_t *target = gnode_array_get(decls, i);
bool target_is_global = ISA(target, NODE_LIST_STAT);
bool target_is_function = ISA(target, NODE_FUNCTION_DECL);
bool target_is_class = ISA(target, NODE_CLASS_DECL);
bool target_is_module = ISA(target, NODE_MODULE_DECL);
if (target_is_function) ++nf;
else if (target_is_class) ++nc;
gnode_t *symbol = lookup_node(target, identifier);
if (symbol && target_is_function && base_is_class) {
REPORT_ERROR(node, "Unable to access local func var %s from within a class.", identifier);
}
if (!symbol && target_is_class) {
gnode_class_decl_t *c = (gnode_class_decl_t *)target;
gnode_class_decl_t *super = (gnode_class_decl_t *)c->superclass;
while (super) {
symbol = lookup_node((gnode_t *)super, identifier);
if (symbol) {
if (NODE_ISA(symbol, NODE_VARIABLE)) {
gnode_var_t *p = (gnode_var_t *)symbol;
if (p->access == TOK_KEY_PRIVATE) {
REPORT_ERROR(node, "Forbidden access to private ivar %s from a subclass.", p->identifier);
}
}
break;
}
super = (gnode_class_decl_t *)super->superclass;
}
}
if (!symbol) continue;
if (target_is_global) {
SET_NODE_LOCATION(node, LOCATION_GLOBAL, 0, 0);
DEBUG_LOOKUP("Identifier %s found in GLOBALS", identifier);
node->symbol = symbol;
return symbol;
}
uint16_t index = UINT16_MAX;
if (NODE_ISA(symbol, NODE_VARIABLE)) {
gnode_var_t *p = (gnode_var_t *)symbol;
index = p->index;
}
if (target_is_function) {
if (nf > 1) {
assert(ISA(base_node, NODE_FUNCTION_DECL));
gnode_var_t *var = (gnode_var_t *)symbol;
gnode_function_decl_t *f = ((gnode_function_decl_t *)base_node);
uint16_t n = nf - 1;
gupvalue_t *upvalue = gnode_function_add_upvalue(f, var, n);
uint16_t idx = len - 2;
while (n > 1) {
f = (gnode_function_decl_t *) gnode_array_get(decls, idx);
gnode_function_add_upvalue(f, var, --n);
--idx;
}
var->upvalue = true;
node->upvalue = upvalue;
SET_NODE_LOCATION(node, LOCATION_UPVALUE, index, nf);
} else {
SET_NODE_LOCATION(node, LOCATION_LOCAL, index, nf);
}
DEBUG_LOOKUP("Identifier %s found in FUNCTION %s (nf: %lu index: %d)", identifier, ((gnode_function_decl_t *)target)->identifier, nf-1, index);
}
else if (target_is_class) {
SET_NODE_LOCATION(node, (nc == 1) ? LOCATION_CLASS_IVAR_SAME : LOCATION_CLASS_IVAR_OUTER, index, nc-1);
DEBUG_LOOKUP("Identifier %s found in CLASS %s (up to %zu outer levels)", identifier, ((gnode_class_decl_t *)target)->identifier, nc-1);
}
else if (target_is_module) {
}
else {
assert(0);
}
node->symbol = symbol;
return symbol;
}
DEBUG_LOOKUP("Identifier %s NOT FOUND\n", identifier);
return NULL;
}
static gnode_t *lookup_symtable_id (gvisitor_t *self, gnode_identifier_expr_t *id, bool isclass) {
gnode_t *target = NULL;
gnode_t *target1 = lookup_identifier(self, id->value, id);
if (!target1) {REPORT_ERROR((gnode_t *)id, "%s %s not found.", (isclass) ? "Class" : "Protocol", id->value); return NULL;}
target = target1;
if (id->value2) {
gnode_t *target2 = lookup_node(target1, id->value2);
if (!target2) {REPORT_ERROR((gnode_t *)id, "%s %s not found in %s.", (isclass) ? "Class" : "Protocol", id->value2, id->value); return NULL;}
target = target2;
}
return target;
}
static bool is_expression (gnode_t *node) {
gnode_n tag = NODE_TAG(node);
return ((tag >= NODE_BINARY_EXPR) && (tag <= NODE_ACCESS_EXPR));
}
static bool is_expression_assignment (gnode_t *node) {
gnode_n tag = NODE_TAG(node);
if (tag == NODE_BINARY_EXPR) {
gnode_binary_expr_t *expr = (gnode_binary_expr_t *)node;
return (expr->op == TOK_OP_ASSIGN);
}
return false;
}
static bool is_expression_range (gnode_t *node) {
gnode_n tag = NODE_TAG(node);
if (tag == NODE_BINARY_EXPR) {
gnode_binary_expr_t *expr = (gnode_binary_expr_t *)node;
return ((expr->op == TOK_OP_RANGE_INCLUDED) || (expr->op == TOK_OP_RANGE_EXCLUDED));
}
return false;
}
static bool is_expression_valid (gnode_t *node) {
if (!node) return false;
gnode_n tag = NODE_TAG(node);
switch (tag) {
case NODE_UNARY_EXPR: {
return is_expression_valid(((gnode_unary_expr_t *)node)->expr);
}
case NODE_BINARY_EXPR: {
gnode_binary_expr_t *expr = (gnode_binary_expr_t *)node;
if (expr->op == TOK_OP_ASSIGN) return false;
if (!is_expression_valid(expr->left)) return false;
return is_expression_valid(expr->right);
}
case NODE_IDENTIFIER_EXPR: {
return true;
}
case NODE_MODULE_DECL:
case NODE_ENUM_DECL: {
return false;
}
default: break;
}
return true;
}
static bool is_init_function (gnode_t *node) {
if (ISA(node, NODE_FUNCTION_DECL)) {
gnode_function_decl_t *f = (gnode_function_decl_t *)node;
if (!f->identifier) return false;
return (strcmp(f->identifier, CLASS_CONSTRUCTOR_NAME) == 0);
}
return false;
}
static bool is_init_infinite_loop(gvisitor_t *self, gnode_identifier_expr_t *identifier, gnode_r *list) {
gnode_r *decls = ((semacheck_t *)self->data)->declarations;
size_t len = gnode_array_size(decls);
if (len < 2) return false;
if (!is_init_function(gnode_array_get(decls, len-1))) return false;
gnode_t *target_node = gnode_array_get(decls, len-2);
if (!ISA(target_node, NODE_CLASS_DECL)) return false;
bool continue_check = false;
if (identifier->symbol) continue_check = target_node == identifier->symbol;
else continue_check = ((identifier->value) && (strcmp(identifier->value, SELF_PARAMETER_NAME) == 0));
if (!continue_check) return false;
size_t count = gnode_array_size(list);
if (count < 1) return false;
gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *)gnode_array_get(list, 0);
return (subnode->base.tag == NODE_CALL_EXPR);
}
static void check_access_storage_specifiers (gvisitor_t *self, gnode_t *node, gnode_n env, gtoken_t access, gtoken_t storage) {
if (NODE_TAG(node) == NODE_MODULE_DECL) {
if (access != 0) REPORT_ERROR(node, "Access specifier cannot be used for module.");
if (storage != 0) REPORT_ERROR(node, "Storage specifier cannot be used for module.");
}
if ((access != 0) && (env != NODE_CLASS_DECL) && (env != NODE_MODULE_DECL)) {
REPORT_ERROR(node, "Access specifier does not make sense here.");
}
if ((storage == TOK_KEY_STATIC) && (env != NODE_CLASS_DECL)) {
REPORT_ERROR(node, "Static storage specifier does not make sense outside a class declaration.");
}
}
static bool check_assignment_expression (gvisitor_t *self, gnode_binary_expr_t *node) {
gnode_n tag = NODE_TAG(node->left);
bool result = ((tag == NODE_IDENTIFIER_EXPR) || (tag == NODE_FILE_EXPR) || (tag == NODE_POSTFIX_EXPR));
if (tag == NODE_POSTFIX_EXPR) {
gnode_postfix_expr_t *expr = (gnode_postfix_expr_t *)node->left;
if (ISA(expr->id, NODE_LITERAL_EXPR)) {
result = false;
} else {
size_t count = gnode_array_size(expr->list);
gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *)gnode_array_get(expr->list, count-1);
result = (NODE_TAG(subnode) != NODE_CALL_EXPR);
}
}
node->left->is_assignment = result;
if (!result) REPORT_ERROR(node->left, "Wrong assignment expression.");
return result;
}
static bool check_range_expression (gvisitor_t *self, gnode_binary_expr_t *node) {
gnode_t *r[2] = {node->left, node->right};
for (int i=0; i<2; ++i) {
gnode_t *range = r[i];
if (ISA_LITERAL(range)) {
gnode_literal_expr_t *expr = (gnode_literal_expr_t *)range;
if (expr->type != LITERAL_INT) {REPORT_ERROR(node, "Range must be integer.");} return false;
}
}
return true;
}
static bool check_class_ivar (gvisitor_t *self, gnode_class_decl_t *classnode, gnode_variable_decl_t *node) {
size_t count = gnode_array_size(node->decls);
gnode_class_decl_t *supernode = (gnode_class_decl_t *)classnode->superclass;
for (size_t i=0; i<count; ++i) {
gnode_var_t *p = (gnode_var_t *)gnode_array_get(node->decls, i);
DEBUG_SEMANTIC("check_ivar %s", p->identifier);
if (string_cmp(p->identifier, OUTER_IVAR_NAME) == 0) continue;
while (supernode) {
symboltable_t *symtable = supernode->symtable;
if (symboltable_lookup(symtable, p->identifier) != NULL) {
REPORT_WARNING((gnode_t *)node, "Property '%s' defined in class '%s' already defined in its superclass %s.", p->identifier, classnode->identifier, supernode->identifier);
}
supernode = (gnode_class_decl_t *)supernode->superclass;
}
}
return true;
}
static void free_postfix_subexpr (gnode_postfix_subexpr_t *subnode) {
if (subnode->base.refcount > 0) {--subnode->base.refcount; return;}
gnode_n tag = subnode->base.tag;
if (tag == NODE_CALL_EXPR) {
if (subnode->args) {
gnode_array_each(subnode->args, gnode_free(val););
gnode_array_free(subnode->args);
}
} else {
gnode_free(subnode->expr);
}
mem_free((gnode_t*)subnode);
}
static void visit_list_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
DEBUG_SEMANTIC("visit_list_stmt");
PUSH_DECLARATION(node);
gnode_array_each(node->stmts, {visit(val);});
POP_DECLARATION();
}
static void visit_compound_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
DEBUG_SEMANTIC("visit_compound_stmt");
gnode_t *top = TOP_DECLARATION();
symboltable_t *symtable = symtable_from_node(top);
symboltable_enter_scope(symtable);
gnode_array_each(node->stmts, {visit(val);});
symboltable_exit_scope(symtable, &node->nclose);
}
static void visit_label_stmt (gvisitor_t *self, gnode_label_stmt_t *node) {
DEBUG_SEMANTIC("visit_label_stmt");
gtoken_t type = NODE_TOKEN_TYPE(node);
if (!TOP_STATEMENT_ISA_SWITCH()) {
if (type == TOK_KEY_DEFAULT) REPORT_ERROR(node, "'default' statement not in switch statement.");
if (type == TOK_KEY_CASE) REPORT_ERROR(node, "'case' statement not in switch statement.");
}
if (type == TOK_KEY_DEFAULT) {visit(node->stmt);}
else if (type == TOK_KEY_CASE) {visit(node->expr); visit(node->stmt);}
}
static void visit_flow_stmt (gvisitor_t *self, gnode_flow_stmt_t *node) {
DEBUG_SEMANTIC("visit_flow_stmt");
if (is_expression_assignment(node->cond)) REPORT_ERROR(node->cond, "Assignment not allowed here");
gtoken_t type = NODE_TOKEN_TYPE(node);
if (type == TOK_KEY_IF) {
visit(node->cond);
visit(node->stmt);
if (node->elsestmt) visit(node->elsestmt);
} else if (type == TOK_KEY_SWITCH) {
PUSH_STATEMENT(type);
visit(node->cond);
visit(node->stmt);
POP_STATEMENT();
}
}
static void visit_loop_stmt (gvisitor_t *self, gnode_loop_stmt_t *node) {
DEBUG_SEMANTIC("visit_loop_stmt");
gtoken_t type = NODE_TOKEN_TYPE(node);
PUSH_STATEMENT(type);
const char *LOOP_NAME;
gnode_t *cond;
if (type == TOK_KEY_WHILE) {LOOP_NAME = "WHILE"; cond = node->cond;}
else if (type == TOK_KEY_REPEAT) {LOOP_NAME = "REPEAT"; cond = node->expr;}
else if (type == TOK_KEY_FOR) {LOOP_NAME = "FOR"; cond = node->cond;}
else {cond = NULL; assert(0);}
if (is_expression_assignment(cond))
REPORT_ERROR(cond, "Assignments in Gravity does not return a value so cannot be used inside a %s condition.", LOOP_NAME);
if (type == TOK_KEY_FOR) {
bool type_check = (NODE_ISA(node->cond, NODE_VARIABLE_DECL) || NODE_ISA(node->cond, NODE_IDENTIFIER_EXPR));
if (!type_check) REPORT_ERROR(cond, "FOR declaration must be a variable declaration or a local identifier.");
if (NODE_ISA(node->cond, NODE_VARIABLE_DECL)) {
gnode_variable_decl_t *var = (gnode_variable_decl_t *)node->cond;
if (gnode_array_size(var->decls) > 1) REPORT_ERROR(cond, "Cannot declare more than one variable inside a FOR loop.");
gnode_var_t *p = (gnode_var_t *)gnode_array_get(var->decls, 0);
if (p->expr) REPORT_ERROR(cond, "Assignment expression prohibited in a FOR loop.");
}
}
if (type == TOK_KEY_WHILE) {
visit(node->cond);
visit(node->stmt);
} else if (type == TOK_KEY_REPEAT) {
visit(node->stmt);
visit(node->expr);
} else if (type == TOK_KEY_FOR) {
symboltable_t *symtable = symtable_from_node(TOP_DECLARATION());
symboltable_enter_scope(symtable);
visit(node->cond);
if (NODE_ISA(node->cond, NODE_IDENTIFIER_EXPR)) {
gnode_identifier_expr_t *expr = (gnode_identifier_expr_t *)node->cond;
if (expr->location.type != LOCATION_LOCAL) REPORT_ERROR(cond, "FOR declaration must be a variable declaration or a local identifier.");
}
visit(node->expr);
visit(node->stmt);
symboltable_exit_scope(symtable, &node->nclose);
}
POP_STATEMENT();
}
static void visit_jump_stmt (gvisitor_t *self, gnode_jump_stmt_t *node) {
DEBUG_SEMANTIC("visit_jump_stmt");
gtoken_t type = NODE_TOKEN_TYPE(node);
if (type == TOK_KEY_BREAK) {
if (!(TOP_STATEMENT_ISA_LOOP() || TOP_STATEMENT_ISA_SWITCH()))
REPORT_ERROR(node, "'break' statement not in loop or switch statement.");
}
else if (type == TOK_KEY_CONTINUE) {
if (!TOP_STATEMENT_ISA_LOOP()) REPORT_ERROR(node, "'continue' statement not in loop statement.");
}
else if (type == TOK_KEY_RETURN) {
gnode_t *n1 = TOP_DECLARATION();
if (!ISA(n1, NODE_FUNCTION_DECL)) REPORT_ERROR(node, "'return' statement not in a function definition.");
if (node->expr) {
visit(node->expr);
if (!is_expression_valid(node->expr)) {
REPORT_ERROR(node->expr, "Invalid expression.");
return;
}
}
} else {
assert(0);
}
}
static void visit_empty_stmt (gvisitor_t *self, gnode_empty_stmt_t *node) {
DEBUG_SEMANTIC("visit_empty_stmt");
gnode_t *top = TOP_DECLARATION();
if (!NODE_ISA_FUNCTION(top)) REPORT_ERROR(node, "Extraneous semicolon error.");
return;
}
static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node) {
DEBUG_SEMANTIC("visit_function_decl %s", node->identifier);
gnode_t *top = TOP_DECLARATION();
check_access_storage_specifiers(self, (gnode_t *)node, NODE_TAG(top), node->access, node->storage);
node->env = top;
PUSH_DECLARATION(node);
symboltable_t *symtable = symboltable_create(false);
symboltable_enter_scope(symtable);
node->symtable = symtable;
if (node->params) {
gnode_array_each(node->params, {
gnode_var_t *p = (gnode_var_t *)val;
p->env = (gnode_t*)node;
if (!symboltable_insert(symtable, p->identifier, (void *)p)) REPORT_ERROR(p, "Parameter %s redeclared.", p->identifier);
SET_LOCAL_INDEX(p, symtable);
DEBUG_SEMANTIC("Local:%s index:%d", p->identifier, p->index);
});
}
gnode_compound_stmt_t *block = node->block;
if (block) {gnode_array_each(block->stmts, {visit(val);});}
uint16_t nparams = (node->params) ? (uint16_t)marray_size(*node->params) : 0;
uint32_t nlocals = symboltable_exit_scope(symtable, NULL);
if (nlocals > MAX_LOCALS) REPORT_ERROR(node, "Maximum number of local variables reached in function %s (max:%d found:%d).",
node->identifier, MAX_LOCALS, nlocals);
node->nlocals = (uint16_t)nlocals - nparams;
node->nparams = nparams;
uint32_t nupvalues = (node->uplist) ? (uint32_t)marray_size(*node->uplist) : 0;
if (nupvalues > MAX_UPVALUES) REPORT_ERROR(node, "Maximum number of upvalues reached in function %s (max:%d found:%d).",
node->identifier, MAX_LOCALS, nupvalues);
POP_DECLARATION();
DEBUG_SEMANTIC("MAX LOCALS for function %s: %d", node->identifier, node->nlocals);
}
static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node) {
gnode_t *top = TOP_DECLARATION();
symboltable_t *symtable = symtable_from_node(top);
size_t count = gnode_array_size(node->decls);
gnode_n env = NODE_TAG(top);
bool env_is_function = (env == NODE_FUNCTION_DECL);
check_access_storage_specifiers(self, (gnode_t *)node, env, node->access, node->storage);
for (size_t i=0; i<count; ++i) {
gnode_var_t *p = (gnode_var_t *)gnode_array_get(node->decls, i);
DEBUG_SEMANTIC("visit_variable_decl %s", p->identifier);
p->env = top;
if (env_is_function) {
if (!symboltable_insert(symtable, p->identifier, (void *)p)) REPORT_ERROR(p, "Identifier %s redeclared.", p->identifier);
SET_LOCAL_INDEX(p, symtable);
DEBUG_SEMANTIC("Local:%s index:%d", p->identifier, p->index);
} else if (env == NODE_CLASS_DECL) {
gnode_class_decl_t *c = (gnode_class_decl_t *)top;
uint32_t n1 = (node->storage == TOK_KEY_STATIC) ? c->nsvar++ : c->nivar++;
uint32_t n2 = 0;
gnode_class_decl_t *super = (gnode_class_decl_t *)c->superclass;
while (super) {
n2 = (node->storage == TOK_KEY_STATIC) ? super->nsvar : super->nivar;
super = (gnode_class_decl_t *)super->superclass;
}
p->index = n1+n2;
DEBUG_SEMANTIC("Class: %s property:%s index:%d (static %d)", c->identifier, p->identifier, p->index, (node->storage == TOK_KEY_STATIC));
}
if (p->expr) visit(p->expr);
}
}
static void visit_enum_decl (gvisitor_t *self, gnode_enum_decl_t *node) {
DEBUG_SEMANTIC("visit_enum_decl %s", node->identifier);
gnode_t *top = TOP_DECLARATION();
check_access_storage_specifiers(self, (gnode_t *)node, NODE_TAG(top), node->access, node->storage);
}
static void visit_class_decl (gvisitor_t *self, gnode_class_decl_t *node) {
DEBUG_SEMANTIC("visit_class_decl %s", node->identifier);
gnode_t *top = TOP_DECLARATION();
check_access_storage_specifiers(self, (gnode_t *)node, NODE_TAG(top), node->access, node->storage);
node->env = top;
if (node->superclass) {
gnode_identifier_expr_t *id = (gnode_identifier_expr_t *)node->superclass;
gnode_t *target = lookup_symtable_id(self, id, true);
if (!target) REPORT_ERROR(id, "Unable to find superclass %s for class %s.", id->value, node->identifier);
node->superclass = target;
gnode_free((gnode_t*)id);
}
PUSH_DECLARATION(node);
gnode_array_each(node->decls, {
if ((node->superclass) && (ISA_VAR_DECLARATION(val))) {
check_class_ivar(self, (gnode_class_decl_t *)node, (gnode_variable_decl_t *)val);
}
visit(val);
});
POP_DECLARATION();
}
static void visit_module_decl (gvisitor_t *self, gnode_module_decl_t *node) {
DEBUG_SEMANTIC("visit_module_decl %s", node->identifier);
gnode_t *top = TOP_DECLARATION();
node->env = top;
if (NODE_TAG(top) != NODE_LIST_STAT) REPORT_ERROR(node, "Module %s cannot be declared here.", node->identifier);
check_access_storage_specifiers(self, (gnode_t *)node, NODE_TAG(top), node->access, node->storage);
PUSH_DECLARATION(node);
gnode_array_each(node->decls, {visit(val);});
POP_DECLARATION();
}
static void visit_binary_expr (gvisitor_t *self, gnode_binary_expr_t *node) {
DEBUG_SEMANTIC("visit_binary_expr %s", token_name(node->op));
if (!is_expression(node->left)) REPORT_ERROR(node->left, "LValue must be an expression.");
if (!is_expression(node->right)) REPORT_ERROR(node->right, "RValue must be an expression.");
visit(node->left);
visit(node->right);
if (!is_expression_valid(node->left)) REPORT_ERROR(node->left, "Invalid left expression.");
if (!is_expression_valid(node->right)) REPORT_ERROR(node->right, "Invalid right expression.");
if (is_expression_assignment((gnode_t*)node)) check_assignment_expression(self, node);
else if (is_expression_range((gnode_t*)node)) check_range_expression(self, node);
}
static void visit_unary_expr (gvisitor_t *self, gnode_unary_expr_t *node) {
DEBUG_SEMANTIC("visit_unary_expr %s", token_name(node->op));
visit(node->expr);
if (!is_expression_valid(node->expr)) REPORT_ERROR(node->expr, "Invalid expression.");
}
static void visit_postfix_expr (gvisitor_t *self, gnode_postfix_expr_t *node) {
DEBUG_SEMANTIC("visit_postfix_expr");
visit(node->id);
gnode_t *target = NULL;
if (ISA(node->id, NODE_IDENTIFIER_EXPR)) {
target = ((gnode_identifier_expr_t *)node->id)->symbol;
if (ISA(target, NODE_VARIABLE)) target = NULL;
}
if (ISA(target, NODE_ENUM_DECL)) {
gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, 0);
gnode_n tag = subnode->base.tag;
if (tag != NODE_ACCESS_EXPR) {REPORT_ERROR(node->id, "Invalid enum expression."); return;}
if (node->base.is_assignment) {REPORT_ERROR(node, "Assignment not allowed for an enum type."); return;}
if (!ISA(subnode->expr, NODE_IDENTIFIER_EXPR)) {REPORT_ERROR(subnode, "Invalid enum expression."); return;}
gnode_identifier_expr_t *expr = (gnode_identifier_expr_t *)subnode->expr;
const char *value = expr->value;
gnode_t *v = lookup_node(target, value);
if (!v) {REPORT_ERROR(subnode, "Unable to find %s in enum %s.", value, ((gnode_enum_decl_t *)target)->identifier); return;}
size_t n = gnode_array_size(node->list);
if (n == 1) {
gnode_free(node->id);
gnode_postfix_subexpr_t *subexpr = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, 0);
free_postfix_subexpr(subexpr);
gnode_array_free(node->list);
node->list = NULL;
node->id = gnode_duplicate(v, false);
} else {
gnode_free(node->id);
node->id = gnode_duplicate(v, false);
gnode_postfix_subexpr_t *subexpr = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, 0);
free_postfix_subexpr(subexpr);
node->list = gnode_array_remove_byindex(node->list, 0);
}
return;
}
if (ISA(node->id, NODE_IDENTIFIER_EXPR)) {
if (is_init_infinite_loop(self, (gnode_identifier_expr_t *)node->id, node->list)) {
REPORT_ERROR(node, "Infinite loop detected in init func.");
}
}
bool is_super = (NODE_ISA(node->id, NODE_KEYWORD_EXPR) && (((gnode_keyword_expr_t *)node->id)->base.token.type == TOK_KEY_SUPER));
bool is_assignment = node->base.is_assignment;
size_t count = gnode_array_size(node->list);
for (size_t i=0; i<count; ++i) {
gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, i);
gnode_n tag = subnode->base.tag;
bool is_real_assigment = (is_assignment && (i+1 == count));
if (is_real_assigment) {
if (tag == NODE_CALL_EXPR) {REPORT_ERROR((gnode_t *)subnode, "Unable to assign a value to a function call."); return;}
if (is_super) {REPORT_ERROR((gnode_t *)subnode, "Unable to explicitly modify super."); return;}
}
if (tag == NODE_CALL_EXPR) {
size_t n = gnode_array_size(subnode->args);
for (size_t j=0; j<n; ++j) {
gnode_t *val = (gnode_t *)gnode_array_get(subnode->args, j);
visit(val);
}
continue;
}
if (tag == NODE_SUBSCRIPT_EXPR) {
if (subnode->expr) visit(subnode->expr);
continue;
}
if (tag == NODE_ACCESS_EXPR) {
if (!ISA(subnode->expr, NODE_IDENTIFIER_EXPR)) REPORT_ERROR(subnode->expr, "Invalid access expression.");
continue;
}
DEBUG_SEMANTIC("UNRECOGNIZED POSTFIX OPTIONAL EXPRESSION");
assert(0);
}
}
static void visit_file_expr (gvisitor_t *self, gnode_file_expr_t *node) {
DEBUG_SEMANTIC("visit_file_expr");
gnode_r *decls = ((semacheck_t *)self->data)->declarations;
gnode_t *globals = gnode_array_get(decls, 0);
gnode_t *target = globals;
size_t n = gnode_array_size(node->identifiers);
assert(n);
n = 1;
for (size_t i=0; i<n; ++i) {
const char *identifier = gnode_array_get(node->identifiers, i);
DEBUG_SEMANTIC("LOOKUP %s", identifier);
gnode_t *symbol = lookup_node(target, identifier);
if (!symbol) {REPORT_ERROR(node, "Module identifier %s not found.", identifier); break;}
SET_NODE_LOCATION(node, LOCATION_GLOBAL, 0, 0);
target = symbol;
}
}
static void visit_literal_expr (gvisitor_t *self, gnode_literal_expr_t *node) {
#pragma unused(self, node)
#if GRAVITY_SEMANTIC_DEBUG
char value[256];
gnode_literal_dump(node, value, sizeof(value));
DEBUG_SEMANTIC("visit_literal_expr %s", value);
DEBUG_SEMANTIC("end visit_literal_expr");
#endif
}
static void visit_identifier_expr (gvisitor_t *self, gnode_identifier_expr_t *node) {
DEBUG_SEMANTIC("visit_identifier_expr %s", node->value);
gnode_t *symbol = lookup_identifier(self, node->value, node);
if (!symbol) REPORT_ERROR(node, "Identifier %s not found.", node->value);
}
static void visit_keyword_expr (gvisitor_t *self, gnode_keyword_expr_t *node) {
#pragma unused(self, node)
DEBUG_SEMANTIC("visit_keyword_expr %s", token_name(node->base.token.type));
}
static void visit_list_expr (gvisitor_t *self, gnode_list_expr_t *node) {
size_t n = gnode_array_size(node->list1);
bool ismap = (node->list2 != NULL);
DEBUG_SEMANTIC("visit_list_expr (n: %zu ismap: %d)", n, ismap);
for (size_t j=0; j<n; ++j) {
gnode_t *e = gnode_array_get(node->list1, j);
visit(e);
if (ismap) {
for (size_t k=0; k<n; ++k) {
if (k == j) continue;
gnode_t *key = gnode_array_get(node->list1, k);
if (gnode_is_equal(e, key)) {
if (gnode_is_literal_string(key)) {
gnode_literal_expr_t *v = (gnode_literal_expr_t *)key;
REPORT_ERROR(key, "Duplicated key %s in map.", v->value.str);
} else REPORT_ERROR(key, "Duplicated key in map.");
}
}
e = gnode_array_get(node->list2, j);
visit(e);
}
}
}
bool gravity_semacheck2 (gnode_t *node, gravity_delegate_t *delegate) {
semacheck_t data = {.declarations = gnode_array_create(), .lasterror = 0};
marray_init(data.statements);
gvisitor_t visitor = {
.nerr = 0,
.data = (void *)&data,
.delegate = (void *)delegate,
.visit_list_stmt = visit_list_stmt,
.visit_compound_stmt = visit_compound_stmt,
.visit_label_stmt = visit_label_stmt,
.visit_flow_stmt = visit_flow_stmt,
.visit_loop_stmt = visit_loop_stmt,
.visit_jump_stmt = visit_jump_stmt,
.visit_empty_stmt = visit_empty_stmt,
.visit_function_decl = visit_function_decl,
.visit_variable_decl = visit_variable_decl,
.visit_enum_decl = visit_enum_decl,
.visit_class_decl = visit_class_decl,
.visit_module_decl = visit_module_decl,
.visit_binary_expr = visit_binary_expr,
.visit_unary_expr = visit_unary_expr,
.visit_file_expr = visit_file_expr,
.visit_literal_expr = visit_literal_expr,
.visit_identifier_expr = visit_identifier_expr,
.visit_keyword_expr = visit_keyword_expr,
.visit_list_expr = visit_list_expr,
.visit_postfix_expr = visit_postfix_expr,
};
DEBUG_SEMANTIC("=== SEMANTIC CHECK STEP 2 ===");
gvisit(&visitor, node);
DEBUG_SEMANTIC("\n");
marray_destroy(data.statements);
gnode_array_free(data.declarations);
return (visitor.nerr == 0);
}