diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | examples/std.ek | 6 | ||||
| -rw-r--r-- | hmm.t | 54 | ||||
| -rw-r--r-- | include/ek/ast.h | 13 | ||||
| -rw-r--r-- | src/actualize.c | 196 | ||||
| -rw-r--r-- | src/ast.c | 37 | ||||
| -rw-r--r-- | src/parser.y | 5 | ||||
| -rw-r--r-- | tests/trait_expand.ek | 7 | ||||
| -rw-r--r-- | tests/trait_multiple_expand.ek | 18 | ||||
| -rw-r--r-- | tests/trait_recursive_expand.ek | 8 | ||||
| -rw-r--r-- | tests/type_expand.ek | 11 |
11 files changed, 254 insertions, 102 deletions
@@ -3,5 +3,6 @@ docs/output build gen ek +*.t !gen/.gitkeep !include/ek diff --git a/examples/std.ek b/examples/std.ek index f9eb58c..dbf3a8c 100644 --- a/examples/std.ek +++ b/examples/std.ek @@ -92,6 +92,10 @@ pub typedef string { *i9 buf; } +pub define string(s) { + {.len = sizeof(s), .buf = s} as string; +} + /* result import */ pub typedef result[any T] { *i9 err; @@ -116,7 +120,7 @@ pub define fmt[] { str(*fmt p => string) { /* is this a loop? allowed? */ /* alternative would be {.len = 0, .buf = ""} as string I guess*/ - const r = p.fmt("".str()); + const r = p.fmt(string!("")); if r.err() { abort("error converting to string"); } @@ -1,54 +0,0 @@ -/* common.ek */ -typedef any {} - -/* vec.ek */ -typedef vec { - i27 size; - i27 cap; - *any buf; -} - -insert(*vec v, i27 i, typeof(*v.buf) e) -{ - ... -} - -/* define local vector type */ -define vec!(t) -{ - struct vec [vec] {*t buf;} -} - -/* map.ek */ -typedef hashable { - /* pointers? */ - i27 hash(hashable); -} - -typedef map { - i27 size; - i27 cap; - *hashable keys; - *any values; -} - -// vs. append(*map h, typeof h.keys[0] k, typeof h.values[0] e) -// vs. append(*map h, ?h.keys[0] k, ?h.values[0] e) -// vs. append(*map h, #h.keys[0] k, #h.values[0] e) -// vs. append(*map h, :h.keys[0] k, :h.values[0] e) -// vs something else, hmm -append(*map h, `h.keys[0] k, `h.values[0] e) -{ - ... -} - -/* main.ek */ -main() -{ - vec!(i27) v; - // should automatically find the correct insert (unless the same - // elements as the vec typedef are used somewhere else) - insert(v, 0, 20); - // otherwise, force search as vec? - insert(v as vec, 0, 20); -} diff --git a/include/ek/ast.h b/include/ek/ast.h index ab727eb..7ab03ae 100644 --- a/include/ek/ast.h +++ b/include/ek/ast.h @@ -370,20 +370,16 @@ struct trait_implemented { struct trait_implemented *next; }; -/** - * A trait declaration. - * - * @todo should templates take decls or should it just be for structures? - */ +/** A trait definition. */ struct ast_trait { /** Name of trait. */ struct ast_node *id; /** Parameters to construct concrete type from trait. */ struct ast_node *params; + /** Raw body before expansion. */ + struct ast_node *raw_body; /** Body of trait. */ struct ast_node *body; - /** List of types that implement this trait. */ - struct trait_implemented *impl_by; }; /** Cast. */ @@ -676,6 +672,7 @@ struct ast_struct { struct ast_node *generics; /** Body. */ struct ast_node *body; + struct trait_implemented *implemented_by; }; /** Enum member constant value. */ @@ -1116,7 +1113,7 @@ struct ast_node *gen_alias(struct ast_node *id, struct ast_node *type, struct sr * @return Corresponding AST node. */ struct ast_node *gen_trait(struct ast_node *id, struct ast_node *params, - struct ast_node *body, struct src_loc loc); + struct ast_node *raw_body, struct ast_node *body, struct src_loc loc); /** * Generate import; diff --git a/src/actualize.c b/src/actualize.c index 20ac5ef..60e5108 100644 --- a/src/actualize.c +++ b/src/actualize.c @@ -366,7 +366,7 @@ static int analyze_visibility(struct scope *scope, struct ast_node *node) default: ret = -1; - semantic_error(scope->fctx, node, "unknown top element\n"); + semantic_error(scope->fctx, node, "unknown top element"); break; }; @@ -393,7 +393,7 @@ static int analyze_proc(struct scope *scope, struct ast_node *node) return ret; } -static struct ast_node *do_type_expand(struct scope *scope, struct ast_node *n) +static struct ast_node *analyze_type_expand(struct scope *scope, struct ast_node *n) { assert(n->node_type == AST_TYPE_EXPAND); struct ast_node *trait = file_scope_find_type(scope, AST_TYPE_EXPAND(n).id); @@ -408,7 +408,7 @@ static struct ast_node *do_type_expand(struct scope *scope, struct ast_node *n) } semantic_info(scope->fctx, n, "FIXME: skipping type param check for now"); - struct ast_node *body = AST_TRAIT(trait).body; + struct ast_node *body = AST_TRAIT(trait).raw_body; body = clone_ast_node(body); struct ast_node *pa = AST_TYPE_EXPAND(n).args; @@ -419,6 +419,28 @@ static struct ast_node *do_type_expand(struct scope *scope, struct ast_node *n) return body; } +static int implements_trait(struct ast_node *body, struct ast_node *id) +{ + foreach_node(n, body) { + if (n->node_type != AST_ID) + continue; + + if (same_id(n, id)) + return 1; + } + + return 0; +} + +static void mark_implemented(struct ast_node *n) +{ + assert(n->node_type == AST_TYPE_EXPAND); + /** @todo very hacky but good enough for now */ + struct ast_node *id = AST_TYPE_EXPAND(n).id; + n->node_type = AST_ID; + AST_ID(n).id = strdup(AST_ID(id).id); +} + static int analyze_struct(struct scope *scope, struct ast_node *node) { assert(node->node_type == AST_STRUCT); @@ -432,28 +454,52 @@ static int analyze_struct(struct scope *scope, struct ast_node *node) if (generics) ast_set_flags(node, AST_FLAG_GENERIC); + struct ast_node *type = gen_type(AST_TYPE_STRUCT, node, NULL, node->loc); + foreach_node(n, AST_STRUCT(node).body) { - /* function prototypes are checked later */ - if (n->node_type == AST_PROC && !AST_PROC(n).body) + if (n->node_type != AST_TYPE_EXPAND) continue; - if (n->node_type == AST_TYPE_EXPAND) { - struct ast_node *body = do_type_expand(scope, n); - if (!body) - return -1; + if (implements_trait(AST_STRUCT(node).body, AST_TYPE_EXPAND(n).id)) { + n->node_type = AST_EMPTY; + continue; + } + + if (same_id(AST_STRUCT(node).id, AST_TYPE_EXPAND(n).id)) { + semantic_error(scope->fctx, n, + "recursive trait implementations not allowed"); + return -1; + } - replace_type_id(body, AST_TYPE_EXPAND(n).id, AST_STRUCT(node).id); - *n = *body; + struct ast_node *body = analyze_type_expand(scope, n); + if (!body) { + n->node_type = AST_EMPTY; continue; } + replace_type_id(body, AST_TYPE_EXPAND(n).id, type); + ast_block_last(body)->next = n->next; + n->next = body; + + mark_implemented(n); + } + + foreach_node(n, AST_STRUCT(node).body) { + switch (n->node_type) { + case AST_EMPTY: continue; + case AST_ID: continue; + /* prototypes are checked later */ + case AST_PROC: if (!AST_PROC(n).body) continue; + default: + } + if (analyze_visibility(struct_scope, n)) return -1; } foreach_node(n, AST_STRUCT(node).body) { /* also checks prototypes */ - if (n->node_type == AST_PROC) + if (n->node_type != AST_PROC) continue; if (analyze_proc(struct_scope, n)) @@ -465,7 +511,7 @@ static int analyze_struct(struct scope *scope, struct ast_node *node) if (n->node_type != AST_PROC) continue; - if (!AST_PROC(n).body) + if (AST_PROC(n).body) continue; struct ast_node *proc = scope_find_proc(struct_scope, AST_PROC(n).id); @@ -481,10 +527,12 @@ static int analyze_struct(struct scope *scope, struct ast_node *node) } } - + /** @todo there is the possibility that two different traits add the + * same prototype, which is reported in traits but not structs? */ return 0; } +/* quite a lot of overlap with analyze_struct, kind of ugly I guess */ static int analyze_trait(struct scope *scope, struct ast_node *node) { assert(node->node_type == AST_TRAIT); @@ -495,25 +543,84 @@ static int analyze_trait(struct scope *scope, struct ast_node *node) scope_add_scope(node->scope, trait_scope); node->scope = trait_scope; + /** @todo should probably add in aliases for the traits in scope? */ if (generics) ast_set_flags(node, AST_FLAG_GENERIC); + struct ast_node *type = gen_type(AST_TYPE_TRAIT, node, NULL, node->loc); + + AST_TRAIT(node).body = clone_ast_node(AST_TRAIT(node).raw_body); + /* do type expansions */ foreach_node(n, AST_TRAIT(node).body) { - if (n->node_type == AST_TYPE_EXPAND) { - struct ast_node *body = do_type_expand(scope, n); - if (!body) - return -1; + if (n->node_type != AST_TYPE_EXPAND) + continue; - replace_type_id(body, AST_TYPE_EXPAND(n).id, AST_STRUCT(node).id); - *n = *body; + /* don't re-expand already implemented traits */ + if (implements_trait(AST_TRAIT(node).body, AST_TYPE_EXPAND(n).id)) { + /* not sure about this, but at least we don't have stray + * type expands everywhere */ + n->node_type = AST_EMPTY; continue; } + if (same_id(AST_TRAIT(node).id, AST_TYPE_EXPAND(n).id)) { + semantic_error(scope->fctx, n, + "recursive trait implementations not allowed"); + return -1; + } + + struct ast_node *body = analyze_type_expand(scope, n); + if (!body) { + n->node_type = AST_EMPTY; + continue; + } + + replace_type_id(body, AST_TYPE_EXPAND(n).id, type); + ast_last_node(body)->next = n->next; + n->next = body; + + mark_implemented(n); + } + + /* add all procedure definitions to scope */ + foreach_node(n, AST_TRAIT(node).body) { + /* kind of a hack but these shouldn't be shown to + * analyze_visibility */ + switch (n->node_type) { + case AST_EMPTY: continue; + case AST_ID: continue; + /* prototypes are added later */ + case AST_PROC: if (!AST_PROC(n).body) continue; + default: + } + if (analyze_visibility(trait_scope, n)) return -1; } + /** @todo I should really check that there's just one prototype and one + * implementation of that prototype, not sure what the best approach + * would be. Add a prototypes -list to scopes? */ + + /* add all prototypes that don't have matching definition to scope */ foreach_node(n, AST_TRAIT(node).body) { + if (n->node_type != AST_PROC) + continue; + + if (AST_PROC(n).body) + continue; + + /* prototypes are checked only if there's no implementation */ + if (scope_find_proc(trait_scope, AST_PROC(n).id)) + continue; + + if (analyze_visibility(trait_scope, n)) + return -1; + } + + foreach_node(n, AST_TRAIT(node).body) { + if (n->node_type != AST_PROC); + struct act_state state = {0}; if (analyze_proc(trait_scope, n)) return -1; @@ -524,6 +631,7 @@ static int analyze_trait(struct scope *scope, struct ast_node *node) static int analyze_signs(struct scope *scope, struct ast_node *node) { + /** @todo aliases? */ switch (node->node_type) { case AST_VAR: return analyze_var(scope, node); break; case AST_PROC: return analyze_proc(scope, node); break; @@ -554,7 +662,7 @@ static int analyze(struct scope *scope, struct ast_node *tree) return -1; printf("actualized:\n"); - dump_ast(0, node); + dump_ast_node(0, node); } return 0; @@ -964,8 +1072,18 @@ static int actualize_id(struct act_state *state, { UNUSED(state); assert(id && id->node_type == AST_ID); - /** @todo at the moment we always assume an ID is a variable, but stuff - * like function callbacks should be added in the future */ + /** @todo vars and procs kind of override eachother, i.e. + * do_something(){..} + * ^() do_something; + * + * do_something_else(){ + * ... + * do_something(); // calls variable at the moment + * } + * + * Either add in some syntax to distinguish procedure calls and + * pointer calls or make procs and vars share the same namespace. + * */ struct ast_node *decl = file_scope_find_var(scope, id); if (decl) { id->type = decl->type; @@ -1804,19 +1922,39 @@ static int _replace_type_id(struct ast_node *node, void *data) struct ast_node *id = pair[0]; struct ast_node *replacement = pair[1]; - switch (node->node_type) { - case AST_VAR: replace_id(AST_VAR(node).type, id, replacement); break; - /* I think this might also change some extra stuff we might not want, - * hmm */ - case AST_TYPE: replace_id(node, id, replacement); break; + if (node->node_type != AST_TYPE) + goto next; + + switch (AST_TYPE(node).kind) { + case AST_TYPE_ID: *node = *clone_ast_node(replacement); break; + case AST_TYPE_TRAIT: { + struct ast_node *def = AST_TRAIT_TYPE(node).def; + assert(def); + struct ast_node *name = AST_TRAIT(def).id; + if (same_id(id, name)) + *node = *clone_ast_node(replacement); + break; + } + + case AST_TYPE_STRUCT: { + struct ast_node *def = AST_STRUCT_TYPE(node).def; + assert(def); + + struct ast_node *name = AST_STRUCT(def).id; + if (same_id(id, name)) + *node = *clone_ast_node(replacement); + break; + } default: } +next: return ast_call_on(_replace_type_id, node, data); } static int replace_type_id(struct ast_node *nodes, struct ast_node *id, struct ast_node *replacement) { + assert(replacement->node_type == AST_TYPE); struct ast_node *pair[2] = {id, replacement}; return ast_call_on(_replace_type_id, nodes, pair); } @@ -1848,7 +1986,7 @@ static int actualize_trait(struct act_state *state, struct scope *scope, static int actualize_struct(struct act_state *state, struct scope *scope, struct ast_node *node) { - assert(node->node_type == AST_TRAIT); + assert(node->node_type == AST_STRUCT); foreach_node(n, AST_STRUCT(node).body) { /* there's really only prodcedure body actualization left I * guess, as type stuff was taken care of in the analysis phase @@ -518,14 +518,18 @@ struct ast_node *gen_alias(struct ast_node *id, struct ast_node *type, struct sr return n; } -struct ast_node *gen_trait(struct ast_node *id, struct ast_node *params, - struct ast_node *body, struct src_loc loc) +struct ast_node *gen_trait(struct ast_node *id, + struct ast_node *params, + struct ast_node *raw_body, + struct ast_node *body, + struct src_loc loc) { ALLOC_NODE(n, "trait"); n->node_type = AST_TRAIT; AST_TRAIT(n).id = id; AST_TRAIT(n).params = params; AST_TRAIT(n).body = body; + AST_TRAIT(n).raw_body = raw_body; n->loc = loc; return n; } @@ -1119,6 +1123,16 @@ static void __dump_ast(int depth, struct ast_node *node) } } +void dump_ast_node(int depth, struct ast_node *n) +{ + if (!n) { + dump(depth, "{NULL}\n"); + return; + } + + __dump_ast(depth, n); +} + void dump_ast(int depth, struct ast_node *root) { if (!root) { @@ -1128,7 +1142,7 @@ void dump_ast(int depth, struct ast_node *root) struct ast_node *n = root; do { - __dump_ast(depth, n); + dump_ast_node(depth, n); } while ((n = n->next)); } @@ -1292,7 +1306,7 @@ struct ast_node *clone_ast_node(struct ast_node *node) case AST_TYPE_POINTER: new = gen_type(AST_TYPE_POINTER, - AST_PTR_TYPE(node).base, + clone_ast_node(AST_PTR_TYPE(node).base), NULL, node->loc); break; @@ -1397,6 +1411,7 @@ struct ast_node *clone_ast_node(struct ast_node *node) case AST_TRAIT: new = gen_trait(clone_ast_node(AST_TRAIT(node).id), clone_ast_node(AST_TRAIT(node).params), + clone_ast_node(AST_TRAIT(node).raw_body), clone_ast_node(AST_TRAIT(node).body), node->loc); break; @@ -1483,9 +1498,9 @@ static int call_on_var(int (*call)(struct ast_node *, void *), struct ast_node *node, void *data) { int ret = 0; - ret |= call(node->_var.id, data); - ret |= call(node->_var.type, data); - ret |= call(node->_var.init, data); + ret |= call(AST_VAR(node).id, data); + ret |= call(AST_VAR(node).type, data); + ret |= call(AST_VAR(node).init, data); return ret; } @@ -1620,6 +1635,12 @@ static int call_on_type_construct(int (*call)(struct ast_node *, void *), return ret; } +static int call_on_type_pointer(int (*call)(struct ast_node *, void *), + struct ast_node *node, void *data) +{ + return call(AST_PTR_TYPE(node).base, data); +} + static int call_on_type(int (*call)(struct ast_node *, void *), struct ast_node *node, void *data) { @@ -1632,7 +1653,7 @@ static int call_on_type(int (*call)(struct ast_node *, void *), case AST_TYPE_STRUCT: break; case AST_TYPE_SIGN: ret = call_on_type_sign(call, node, data); break; case AST_TYPE_CONSTRUCT: ret = call_on_type_construct(call, node, data); break; - case AST_TYPE_POINTER: break; + case AST_TYPE_POINTER: ret = call_on_type_pointer(call, node, data); break; case AST_TYPE_PRIMITIVE: break; } diff --git a/src/parser.y b/src/parser.y index ddddcb8..c92ecf1 100644 --- a/src/parser.y +++ b/src/parser.y @@ -672,7 +672,8 @@ alias type_param : id id { - $$ = gen_var($2, $1, NULL, src_loc(@$)); + struct ast_node *t = gen_type(AST_TYPE_ID, $1, NULL, src_loc(@1)); + $$ = gen_var($2, t, NULL, src_loc(@$)); } type_params @@ -685,7 +686,7 @@ opt_type_params trait : "define" id "[" opt_type_params "]" "{" opt_members "}" { - $$ = gen_trait($2, $4, $7, src_loc(@$)); + $$ = gen_trait($2, $4, $7, NULL, src_loc(@$)); } enum_val diff --git a/tests/trait_expand.ek b/tests/trait_expand.ek new file mode 100644 index 0000000..eca1e16 --- /dev/null +++ b/tests/trait_expand.ek @@ -0,0 +1,7 @@ +define b[] { + a![]; +} + +define a[] { + a(*a a); +} diff --git a/tests/trait_multiple_expand.ek b/tests/trait_multiple_expand.ek new file mode 100644 index 0000000..285a6ea --- /dev/null +++ b/tests/trait_multiple_expand.ek @@ -0,0 +1,18 @@ +define b[] { + c![]; + a![]; +} + +define a[] { + c![]; + a(*a a); +} + +define c[] { + c(*c c); +} + +typedef d { + b![]; + c(*d d) {} +} diff --git a/tests/trait_recursive_expand.ek b/tests/trait_recursive_expand.ek new file mode 100644 index 0000000..53012bf --- /dev/null +++ b/tests/trait_recursive_expand.ek @@ -0,0 +1,8 @@ +define b[] { + a![]; +} + +define a[] { + b![]; + a(*a a); +} diff --git a/tests/type_expand.ek b/tests/type_expand.ek new file mode 100644 index 0000000..e58d0a0 --- /dev/null +++ b/tests/type_expand.ek @@ -0,0 +1,11 @@ +define trait[] { + trait_func(*trait a, *trait b); +} + +typedef a[] { + trait![]; +} + +main() { + a![] some_var; +} |
