diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | ast.c | 71 | ||||
| -rw-r--r-- | ast.h | 9 | ||||
| -rw-r--r-- | builtins/util.h | 1 | ||||
| -rw-r--r-- | compile.c | 85 | ||||
| -rw-r--r-- | enums.h | 2 | ||||
| -rw-r--r-- | environment.c | 6 | ||||
| -rw-r--r-- | interfaces.c | 120 | ||||
| -rw-r--r-- | interfaces.h | 10 | ||||
| -rw-r--r-- | parse.c | 38 | ||||
| -rw-r--r-- | typecheck.c | 68 | ||||
| -rw-r--r-- | types.c | 41 | ||||
| -rw-r--r-- | types.h | 7 |
13 files changed, 437 insertions, 23 deletions
@@ -29,7 +29,7 @@ BUILTIN_OBJS=builtins/array.o builtins/bool.o builtins/nums.o builtins/functions all: libtomo.so tomo -tomo: tomo.c SipHash/halfsiphash.o ast.o parse.o environment.o types.o typecheck.o structs.o enums.o compile.o repl.o +tomo: tomo.c SipHash/halfsiphash.o ast.o parse.o environment.o types.o typecheck.o structs.o enums.o interfaces.o compile.o repl.o libtomo.so: $(BUILTIN_OBJS) SipHash/halfsiphash.o $(CC) $^ $(CFLAGS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) -lgc -lcord -lm -lunistring -ldl -Wl,-soname,libtomo.so -shared -o $@ @@ -136,6 +136,7 @@ CORD ast_to_xml(ast_t *ast) T(StructDef, "<StructDef name=\"%s\">%r<namespace>%r</namespace></StructDef>", data.name, arg_list_to_xml(data.fields), ast_to_xml(data.namespace)) T(EnumDef, "<EnumDef name=\"%s\"><tags>%r</tags><namespace>%r</namespace></EnumDef>", data.name, tags_to_xml(data.tags), ast_to_xml(data.namespace)) T(LangDef, "<LangDef name=\"%s\">%r</LangDef>", data.name, ast_to_xml(data.namespace)) + T(InterfaceDef, "<InterfaceDef name=\"%s\">%r%r</InterfaceDef>", data.name, type_ast_to_xml(data.type_parameter), arg_list_to_xml(data.fields)) T(Index, "<Index>%r%r</Index>", optional_tagged("indexed", data.indexed), optional_tagged("index", data.index)) T(FieldAccess, "<FieldAccess field=\"%s\">%r</FieldAccess>", data.field, ast_to_xml(data.fielded)) T(Optional, "<Optional>%r</Optional>", ast_to_xml(data.value)) @@ -150,7 +151,7 @@ CORD ast_to_xml(ast_t *ast) CORD type_ast_to_xml(type_ast_t *t) { - if (!t) return "\x1b[35mNULL\x1b[m"; + if (!t) return "NULL"; switch (t->tag) { #define T(type, ...) case type: { auto data = t->__data.type; (void)data; return CORD_asprintf(__VA_ARGS__); } @@ -195,4 +196,72 @@ bool is_idempotent(ast_t *ast) } } +bool type_ast_eq(type_ast_t *x, type_ast_t *y) +{ + if (x->tag != y->tag) return false; + switch (x->tag) { + case UnknownTypeAST: return true; + case VarTypeAST: return streq(Match(x, VarTypeAST)->name, Match(y, VarTypeAST)->name); + case PointerTypeAST: { + auto x_info = Match(x, PointerTypeAST); + auto y_info = Match(y, PointerTypeAST); + return (x_info->is_optional == y_info->is_optional + && x_info->is_stack == y_info->is_stack + && x_info->is_readonly == y_info->is_readonly + && type_ast_eq(x_info->pointed, y_info->pointed)); + } + case ArrayTypeAST: return type_ast_eq(Match(x, ArrayTypeAST)->item, Match(y, ArrayTypeAST)->item); + case TableTypeAST: { + auto tx = Match(x, TableTypeAST); + auto ty = Match(y, TableTypeAST); + return type_ast_eq(tx->key, ty->key) && type_ast_eq(tx->value, ty->value); + } + case FunctionTypeAST: { + auto x_fn = Match(x, FunctionTypeAST); + auto y_fn = Match(y, FunctionTypeAST); + if (!type_ast_eq(x_fn->ret, y_fn->ret)) + return false; + for (arg_ast_t *x_arg = x_fn->args, *y_arg = y_fn->args; x_arg || y_arg; x_arg = x_arg->next, y_arg = y_arg->next) { + if (!x_arg || !y_arg) return false; + if (!x_arg->type) + errx(1, "I can't compare function types that don't have all explicitly typed args"); + if (!y_arg->type) + errx(1, "I can't compare function types that don't have all explicitly typed args"); + if (!type_ast_eq(x_arg->type, y_arg->type)) + return false; + } + return true; + } + } + return true; +} + +type_ast_t *replace_type_ast(type_ast_t *t, type_ast_t *target, type_ast_t *replacement) +{ + if (!t) return t; + if (type_ast_eq(t, target)) + return replacement; + +#define REPLACED_MEMBER(t, tag, member) ({ t = memcpy(GC_MALLOC(sizeof(type_ast_t)), (t), sizeof(type_ast_t)); Match((struct type_ast_s*)(t), tag)->member = replace_type_ast(Match((t), tag)->member, target, replacement); t; }) + switch (t->tag) { + case UnknownTypeAST: + case VarTypeAST: return t; + case PointerTypeAST: return REPLACED_MEMBER(t, PointerTypeAST, pointed); + case ArrayTypeAST: return REPLACED_MEMBER(t, ArrayTypeAST, item); + case TableTypeAST: { + t = REPLACED_MEMBER(t, TableTypeAST, key); + return REPLACED_MEMBER(t, TableTypeAST, value); + } + case FunctionTypeAST: { + auto fn = Match(t, FunctionTypeAST); + t = REPLACED_MEMBER(t, FunctionTypeAST, ret); + arg_ast_t *args = LIST_MAP(fn->args, old_arg, .type=replace_type_ast(old_arg->type, target, replacement)); + Match((struct type_ast_s*)t, FunctionTypeAST)->args = args; + return t; + } + } + return t; +#undef REPLACED_MEMBER +} + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 @@ -111,7 +111,7 @@ typedef enum { Skip, Stop, Pass, Return, Extern, - StructDef, EnumDef, LangDef, + StructDef, EnumDef, LangDef, InterfaceDef, Index, FieldAccess, Optional, DocTest, Use, @@ -250,6 +250,12 @@ struct ast_s { ast_t *namespace; } LangDef; struct { + const char *name; + type_ast_t *type_parameter; + arg_ast_t *fields; + ast_t *namespace; + } InterfaceDef; + struct { ast_t *indexed, *index; bool unchecked; } Index; @@ -284,5 +290,6 @@ CORD type_ast_to_xml(type_ast_t *ast); int printf_ast(FILE *stream, const struct printf_info *info, const void *const args[]); ast_list_t *get_ast_children(ast_t *ast); bool is_idempotent(ast_t *ast); +type_ast_t *replace_type_ast(type_ast_t *t, type_ast_t *target, type_ast_t *replacement); // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/util.h b/builtins/util.h index a091c40e..1b5664d7 100644 --- a/builtins/util.h +++ b/builtins/util.h @@ -13,7 +13,6 @@ #define streq(a, b) (((a) == NULL && (b) == NULL) || (((a) == NULL) == ((b) == NULL) && strcmp(a, b) == 0)) #define new(t, ...) ((t*)memcpy(GC_MALLOC(sizeof(t)), &(t){__VA_ARGS__}, sizeof(t))) #define copy(obj_ptr) ((__typeof(obj_ptr))memcpy(GC_MALLOC(sizeof(*(obj_ptr))), obj_ptr, sizeof(*(obj_ptr)))) -#define grow(arr, new_size) ((typeof (arr))GC_REALLOC(arr, (sizeof(arr[0]))*(new_size))) #define Match(x, _tag) ((x)->tag == _tag ? &(x)->__data._tag : (errx(1, __FILE__ ":%d This was supposed to be a " # _tag "\n", __LINE__), &(x)->__data._tag)) #define Tagged(t, _tag, ...) new(t, .tag=_tag, .__data._tag={__VA_ARGS__}) @@ -10,6 +10,7 @@ #include "compile.h" #include "enums.h" #include "structs.h" +#include "interfaces.h" #include "environment.h" #include "typecheck.h" #include "builtins/util.h" @@ -107,6 +108,10 @@ CORD compile_type(env_t *env, type_t *t) auto e = Match(t, EnumType); return CORD_all(e->env->file_prefix, e->name, "_t"); } + case InterfaceType: { + auto s = Match(t, InterfaceType); + return CORD_all(s->env->file_prefix, s->name, "_t"); + } case TypeInfoType: return "TypeInfo"; default: compiler_err(NULL, NULL, NULL, "Not implemented"); } @@ -424,7 +429,6 @@ CORD compile_statement(env_t *env, ast_t *ast) return CORD_EMPTY; } case LangDef: { - // TODO: implement auto def = Match(ast, LangDef); CORD_appendf(&env->code->typedefs, "typedef CORD %r%s_t;\n", env->file_prefix, def->name); CORD_appendf(&env->code->typedefs, "extern const TypeInfo %r%s;\n", env->file_prefix, def->name); @@ -434,6 +438,10 @@ CORD compile_statement(env_t *env, ast_t *ast) compile_namespace(env, def->name, def->namespace); return CORD_EMPTY; } + case InterfaceDef: { + compile_interface_def(env, ast); + return CORD_EMPTY; + } case FunctionDef: { auto fndef = Match(ast, FunctionDef); bool is_private = Match(fndef->name, Var)->name[0] == '_'; @@ -810,6 +818,8 @@ CORD compile_statement(env_t *env, ast_t *ast) return CORD_EMPTY; } default: + if (!is_discardable(env, ast)) + code_err(ast, "The result of this statement cannot be discarded"); return CORD_asprintf("(void)%r;", compile(env, ast)); } } @@ -843,8 +853,9 @@ CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color) case TableType: return CORD_asprintf("Table$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case FunctionType: case ClosureType: return CORD_asprintf("Func$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case PointerType: return CORD_asprintf("Pointer$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); - case StructType: case EnumType: return CORD_asprintf("(%r)->CustomInfo.as_text(stack(%r), %r, %r)", - compile_type_info(env, t), expr, color, compile_type_info(env, t)); + case StructType: case EnumType: case InterfaceType: + return CORD_asprintf("(%r)->CustomInfo.as_text(stack(%r), %r, %r)", + compile_type_info(env, t), expr, color, compile_type_info(env, t)); default: compiler_err(NULL, NULL, NULL, "Stringifying is not supported for %T", t); } } @@ -1468,8 +1479,14 @@ CORD compile(env_t *env, ast_t *ast) code = CORD_all(code, compile_type(env, arg_type), " $", arg->name, ", "); } - for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) - (void)compile_statement(body_scope, stmt->ast); + // Find which variables are captured in the closure: + env_t *tmp_scope = fresh_scope(body_scope); + for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) { + if (stmt->next) + (void)compile_statement(tmp_scope, stmt->ast); + else + (void)compile(tmp_scope, stmt->ast); + } CORD userdata; if (Table$length(*fn_ctx.closed_vars) == 0) { @@ -1625,6 +1642,36 @@ CORD compile(env_t *env, ast_t *ast) return CORD_all("Table$clear(", self, ")"); } else code_err(ast, "There is no '%s' method for tables", call->name); } + case InterfaceType: { + auto methodcall = Match(ast, MethodCall); + binding_t *b = get_namespace_binding(env, methodcall->self, methodcall->name); + if (b) { + type_t *fn_t = get_method_type(env, methodcall->self, methodcall->name); + arg_ast_t *args = new(arg_ast_t, .value=methodcall->self, .next=methodcall->args); + return CORD_all(b->code, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, args), ")"); + } else { + auto interface = Match(self_value_t, InterfaceType); + for (arg_t *field = interface->fields; field; field = field->next) { + if (streq(field->name, methodcall->name)) { + env_t tmp_env = *env; + tmp_env.types = new(table_t, .fallback=tmp_env.types); + Table$str_set(tmp_env.types, interface->type_parameter, self_value_t); + type_t *field_t = field->type; + if (field_t->tag == ClosureType) + field_t = Match(field_t, ClosureType)->fn; + + arg_t *type_args = Match(field_t, FunctionType)->args; + CORD args = compile_arguments(env, ast, type_args->next, methodcall->args); + bool is_ptr = Match(field_t, FunctionType)->args->type->tag == PointerType; + return CORD_all("({ ", compile_type(env, self_value_t), " $self = ", + compile_to_pointer_depth(env, methodcall->self, 0, false), "; ", + "$self.", methodcall->name, "(", is_ptr ? CORD_EMPTY : "*", "(", compile_type(env, self_value_t), "*)$self.$obj", + args == CORD_EMPTY ? CORD_EMPTY : ", ", args, "); })"); + } + } + code_err(ast, "There is no method called '%s' on the interface type %s", methodcall->name, interface->name); + } + } default: { auto methodcall = Match(ast, MethodCall); type_t *fn_t = get_method_type(env, methodcall->self, methodcall->name); @@ -1644,10 +1691,29 @@ CORD compile(env_t *env, ast_t *ast) } else if (fn_t->tag == TypeInfoType) { type_t *t = Match(fn_t, TypeInfoType)->type; if (t->tag == StructType) { + // Struct constructor: fn_t = Type(FunctionType, .args=Match(t, StructType)->fields, .ret=t); CORD fn = compile(env, call->fn); return CORD_all(fn, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, call->args), ")"); + } else if (t->tag == InterfaceType) { + // Interface constructor: + if (!call->args) code_err(ast, "You need to provide an argument to this interface constructor"); + if (call->args->next) code_err(ast, "This interface constructor only takes one argument"); + auto interface = Match(t, InterfaceType); + ast_t *impl = call->args->value; + type_t *impl_t = get_type(env, impl); + if (impl_t->tag != PointerType) + impl = WrapAST(impl, HeapAllocate, impl); + CORD c = CORD_all("(", compile_type(env, t), "){", compile(env, impl)); + for (arg_t *field = interface->fields->next; field; field = field->next) { + binding_t *b = get_namespace_binding(env, impl, field->name); + if (!b) code_err(ast, "The type %T doesn't have '%s' for the interface %T", impl_t, field->name, t); + // TODO: typecheck implementation! + c = CORD_all(c, ", (void*)", b->code); + } + return CORD_cat(c, "}"); } else if (t->tag == IntType || t->tag == NumType) { + // Int/Num constructor: if (!call->args || call->args->next) code_err(call->fn, "This constructor takes exactly 1 argument"); type_t *actual = get_type(env, call->args->value); @@ -1816,6 +1882,9 @@ CORD compile(env_t *env, ast_t *ast) } code_err(ast, "There is no '%s' field on tables", f->field); } + case InterfaceType: { + code_err(ast, "Interface field access is not yet implemented"); + } case ModuleType: { const char *name = Match(value_t, ModuleType)->name; env_t *module_env = Table$str_get(*env->imports, name); @@ -1881,7 +1950,7 @@ CORD compile(env_t *env, ast_t *ast) case Extern: code_err(ast, "Externs are not supported yet"); case TableEntry: code_err(ast, "Table entries should not be compiled directly"); case Declare: case Assign: case UpdateAssign: case For: case While: case StructDef: case LangDef: - case EnumDef: case FunctionDef: case Skip: case Stop: case Pass: case Return: case DocTest: + case EnumDef: case FunctionDef: case InterfaceDef: case Skip: case Stop: case Pass: case Return: case DocTest: code_err(ast, "This is not a valid expression"); case Unknown: code_err(ast, "Unknown AST"); } @@ -1937,6 +2006,10 @@ CORD compile_type_info(env_t *env, type_t *t) auto e = Match(t, EnumType); return CORD_all("(&", e->env->file_prefix, e->name, ")"); } + case InterfaceType: { + auto i = Match(t, InterfaceType); + return CORD_all("(&", i->env->file_prefix, i->name, ")"); + } case ArrayType: { type_t *item_t = Match(t, ArrayType)->item_type; return CORD_asprintf("$ArrayInfo(%r)", compile_type_info(env, item_t)); @@ -2,8 +2,6 @@ // Compilation of tagged unions (enums) -#include <gc/cord.h> - #include "ast.h" #include "environment.h" diff --git a/environment.c b/environment.c index ec9ec5d5..f19b7498 100644 --- a/environment.c +++ b/environment.c @@ -321,9 +321,13 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name) return struct_->env ? get_binding(struct_->env, name) : NULL; } case EnumType: { - auto enum_ = Match(cls_type, StructType); + auto enum_ = Match(cls_type, EnumType); return enum_->env ? get_binding(enum_->env, name) : NULL; } + case InterfaceType: { + auto interface = Match(cls_type, InterfaceType); + return interface->env ? get_binding(interface->env, name) : NULL; + } case TypeInfoType: { auto info = Match(cls_type, TypeInfoType); return info->env ? get_binding(info->env, name) : NULL; diff --git a/interfaces.c b/interfaces.c new file mode 100644 index 00000000..e74f85c8 --- /dev/null +++ b/interfaces.c @@ -0,0 +1,120 @@ +// Logic for compiling interfaces +#include <ctype.h> +#include <gc/cord.h> +#include <gc.h> +#include <stdio.h> + +#include "ast.h" +#include "builtins/text.h" +#include "compile.h" +#include "interfaces.h" +#include "environment.h" +#include "typecheck.h" +#include "builtins/util.h" + +static CORD compile_str_method(env_t *env, ast_t *ast) +{ + auto def = Match(ast, InterfaceDef); + CORD full_name = CORD_cat(env->file_prefix, def->name); + const char *name = def->name; + const char *dollar = strrchr(name, '$'); + if (dollar) name = dollar + 1; + CORD str_func = CORD_asprintf("static CORD %r$as_text(%r_t *interface, bool use_color) {\n" + "\tif (!interface) return \"%s\";\n", full_name, full_name, name); + return CORD_all(str_func, "\treturn CORD_asprintf(use_color ? \"\\x1b[0;1m", name, "\\x1b[m<%p>\" : \"", name, "<%p>\", interface->$obj);\n}"); +} + +static CORD compile_compare_method(env_t *env, ast_t *ast) +{ + auto def = Match(ast, InterfaceDef); + CORD full_name = CORD_cat(env->file_prefix, def->name); + return CORD_all("static int ", full_name, "$compare(const ", full_name, "_t *x, const ", full_name, + "_t *y, const TypeInfo *info) {\n" + "(void)info;\n", + "return (x->$obj > y->$obj) - (x->$obj < y->$obj);\n}"); +} + +static CORD compile_equals_method(env_t *env, ast_t *ast) +{ + auto def = Match(ast, InterfaceDef); + CORD full_name = CORD_cat(env->file_prefix, def->name); + return CORD_all("static bool ", full_name, "$equal(const ", full_name, "_t *x, const ", full_name, + "_t *y, const TypeInfo *info) {\n" + "(void)info;\n", + "return (x->$obj == y->$obj);\n}"); +} + +static CORD compile_hash_method(env_t *env, ast_t *ast) +{ + auto def = Match(ast, InterfaceDef); + CORD full_name = CORD_cat(env->file_prefix, def->name); + return CORD_all("static uint32_t ", full_name, "$hash(const ", full_name, "_t *interface, const TypeInfo *info) {\n" + "(void)info;\n" + "uint32_t hash;\n" + "halfsiphash(&interface->$obj, sizeof(void*), TOMO_HASH_VECTOR, (uint8_t*)&hash, sizeof(hash));\n" + "return hash;\n}\n"); +} + +void compile_interface_def(env_t *env, ast_t *ast) +{ + auto def = Match(ast, InterfaceDef); + CORD full_name = CORD_cat(env->file_prefix, def->name); + CORD_appendf(&env->code->typedefs, "typedef struct %r_s %r_t;\n", full_name, full_name); + CORD_appendf(&env->code->typedefs, "#define %r(...) ((%r_t){__VA_ARGS__})\n", full_name, full_name); + + CORD_appendf(&env->code->typecode, "struct %r_s {\nvoid *$obj;\n", full_name); + type_ast_t *replacement_type_ast = NewTypeAST(ast->file, ast->start, ast->end, VarTypeAST, .name=def->name); + for (arg_ast_t *field = def->fields; field; field = field->next) { + type_ast_t *field_type = replace_type_ast(field->type, def->type_parameter, replacement_type_ast); + type_t *field_t = parse_type_ast(env, field_type); + if (field_t->tag == ClosureType) + field_t = Match(field_t, ClosureType)->fn; + CORD decl = compile_declaration(env, field_t, field->name); + CORD_appendf(&env->code->typecode, "%r%s;\n", decl, + field_t->tag == BoolType ? ":1" : ""); + } + CORD_appendf(&env->code->typecode, "};\n"); + + // Typeinfo: + CORD_appendf(&env->code->typedefs, "extern const TypeInfo %r;\n", full_name); + + type_t *t = Table$str_get(*env->types, def->name); + assert(t && t->tag == InterfaceType); + auto interface = Match(t, InterfaceType); + CORD typeinfo = CORD_asprintf("public const TypeInfo %r = {%zu, %zu, {.tag=CustomInfo, .CustomInfo={", + full_name, type_size(t), type_align(t)); + + typeinfo = CORD_all(typeinfo, ".as_text=(void*)", full_name, "$as_text, "); + env->code->funcs = CORD_all(env->code->funcs, compile_str_method(env, ast)); + + if (interface->fields && !interface->fields->next) { // Single member, can just use its methods + type_t *member_t = interface->fields->type; + switch (member_t->tag) { + case TextType: + typeinfo = CORD_all(typeinfo, ".hash=(void*)", type_to_cord(member_t), "$hash", ", "); + // fallthrough + case IntType: case NumType: + typeinfo = CORD_all(typeinfo, ".compare=(void*)", type_to_cord(member_t), "$compare, " + ".equal=(void*)", type_to_cord(member_t), "$equal, "); + // fallthrough + case BoolType: goto got_methods; + default: break; + } + } + if (interface->fields) { + env->code->funcs = CORD_all(env->code->funcs, compile_compare_method(env, ast), + compile_equals_method(env, ast), compile_hash_method(env, ast)); + typeinfo = CORD_all( + typeinfo, + ".compare=(void*)", full_name, "$compare, " + ".equal=(void*)", full_name, "$equal, " + ".hash=(void*)", full_name, "$hash"); + } + got_methods:; + typeinfo = CORD_cat(typeinfo, "}}};\n"); + env->code->typeinfos = CORD_all(env->code->typeinfos, typeinfo); + + compile_namespace(env, def->name, def->namespace); +} + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/interfaces.h b/interfaces.h new file mode 100644 index 00000000..47dc4790 --- /dev/null +++ b/interfaces.h @@ -0,0 +1,10 @@ +#pragma once + +// Compilation of user-defined interfaces + +#include "ast.h" +#include "environment.h" + +void compile_interface_def(env_t *env, ast_t *ast); + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 @@ -91,6 +91,7 @@ static PARSER(parse_var); static PARSER(parse_enum_def); static PARSER(parse_struct_def); static PARSER(parse_lang_def); +static PARSER(parse_interface_def); static PARSER(parse_text); static PARSER(parse_func_def); static PARSER(parse_extern); @@ -1597,6 +1598,7 @@ PARSER(parse_namespace) { if ((stmt=optional(ctx, &pos, parse_struct_def)) ||(stmt=optional(ctx, &pos, parse_enum_def)) ||(stmt=optional(ctx, &pos, parse_lang_def)) + ||(stmt=optional(ctx, &pos, parse_interface_def)) ||(stmt=optional(ctx, &pos, parse_func_def)) ||(stmt=optional(ctx, &pos, parse_use)) ||(stmt=optional(ctx, &pos, parse_linker)) @@ -1632,6 +1634,7 @@ PARSER(parse_file_body) { if ((stmt=optional(ctx, &pos, parse_struct_def)) ||(stmt=optional(ctx, &pos, parse_enum_def)) ||(stmt=optional(ctx, &pos, parse_lang_def)) + ||(stmt=optional(ctx, &pos, parse_interface_def)) ||(stmt=optional(ctx, &pos, parse_func_def)) ||(stmt=optional(ctx, &pos, parse_use)) ||(stmt=optional(ctx, &pos, parse_linker)) @@ -1809,6 +1812,41 @@ PARSER(parse_lang_def) { return NewAST(ctx->file, start, pos, LangDef, .name=name, .namespace=namespace); } +PARSER(parse_interface_def) { + // interface Name(T; field:type, ...): namespace... + const char *start = pos; + if (!match_word(&pos, "interface")) return NULL; + int64_t starting_indent = get_indent(ctx, pos); + spaces(&pos); + const char *name = get_id(&pos); + if (!name) + parser_err(ctx, start, pos, "I expected a name for this interface"); + spaces(&pos); + + if (!match(&pos, "(")) + parser_err(ctx, pos, pos, "I expected a '(' and a list of fields here"); + type_ast_t *type_param = expect(ctx, start, &pos, parse_type, "I couldn't parse the type parameter for this interface"); + whitespace(&pos); + expect_str(ctx, start, &pos, ";", "I expected a ';' here"); + arg_ast_t *fields = parse_args(ctx, &pos, false); + expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this interface definition"); + + ast_t *namespace = NULL; + if (match(&pos, ":")) { + const char *ns_pos = pos; + whitespace(&ns_pos); + int64_t ns_indent = get_indent(ctx, ns_pos); + if (ns_indent > starting_indent) { + pos = ns_pos; + namespace = optional(ctx, &pos, parse_namespace); + } + } + if (!namespace) + namespace = NewAST(ctx->file, pos, pos, Block, .statements=NULL); + + return NewAST(ctx->file, start, pos, InterfaceDef, .name=name, .type_parameter=type_param, .fields=fields, .namespace=namespace); +} + arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos, bool allow_unnamed) { arg_ast_t *args = NULL; diff --git a/typecheck.c b/typecheck.c index b95ef415..5e3d005f 100644 --- a/typecheck.c +++ b/typecheck.c @@ -263,6 +263,41 @@ void bind_statement(env_t *env, ast_t *statement) bind_statement(ns_env, stmt->ast); break; } + case InterfaceDef: { + auto def = Match(statement, InterfaceDef); + if (get_binding(env, def->name)) + code_err(statement, "A %T called '%s' has already been defined", get_binding(env, def->name)->type, def->name); + + env_t *ns_env = namespace_env(env, def->name); + + arg_t *fields = new(arg_t, .name="$obj", .type=Type(PointerType, .pointed=Type(MemoryType))); // interface implementor + type_t *type = Type(InterfaceType, .name=def->name, .fields=fields, .opaque=true, .env=ns_env); // placeholder + type_ast_t *replacement_type_ast = NewTypeAST(statement->file, statement->start, statement->end, VarTypeAST, .name=def->name); + Table$str_set(env->types, def->name, type); + for (arg_ast_t *field_ast = def->fields; field_ast; field_ast = field_ast->next) { + if (!field_ast->type) + code_err(field_ast->value, "Interface fields must have defined types, not default values"); + type_ast_t *field_type = replace_type_ast(field_ast->type, def->type_parameter, replacement_type_ast); + type_t *field_t = parse_type_ast(env, field_type); + if (field_t->tag == ClosureType) + field_t = Match(field_t, ClosureType)->fn; + if ((field_t->tag == InterfaceType && Match(field_t, InterfaceType)->opaque) + || (field_t->tag == EnumType && Match(field_t, EnumType)->opaque)) + code_err(field_ast->type, "This type is recursive and would create an infinitely sized interface. Try using a pointer."); + fields = new(arg_t, .name=field_ast->name, .type=field_t, .default_val=field_ast->value, .next=fields); + } + REVERSE_LIST(fields); + type->__data.InterfaceType.fields = fields; // populate placeholder + type->__data.InterfaceType.opaque = false; + + type_t *typeinfo_type = Type(TypeInfoType, .name=def->name, .type=type, .env=ns_env); + Table$str_set(env->locals, def->name, new(binding_t, .type=typeinfo_type, .code=CORD_all(env->file_prefix, def->name))); + + for (ast_list_t *stmt = def->namespace ? Match(def->namespace, Block)->statements : NULL; stmt; stmt = stmt->next) { + bind_statement(ns_env, stmt->ast); + } + break; + } case Use: { env_t *module_env = load_module(env, statement); for (table_t *bindings = module_env->locals; bindings != module_env->globals; bindings = bindings->fallback) { @@ -552,7 +587,7 @@ type_t *get_type(env_t *env, ast_t *ast) if (fn_type_t->tag == TypeInfoType) { type_t *t = Match(fn_type_t, TypeInfoType)->type; - if (t->tag == StructType || t->tag == IntType || t->tag == NumType) + if (t->tag == StructType || t->tag == InterfaceType || t->tag == IntType || t->tag == NumType) return t; // Constructor code_err(call->fn, "This is not a type that has a constructor"); } @@ -593,6 +628,31 @@ type_t *get_type(env_t *env, ast_t *ast) else if (streq(call->name, "clear")) return Type(VoidType); else code_err(ast, "There is no '%s' method for tables", call->name); } + case InterfaceType: { + auto methodcall = Match(ast, MethodCall); + binding_t *b = get_namespace_binding(env, methodcall->self, methodcall->name); + if (b) { + if (b->type->tag != FunctionType) + code_err(ast, "'%s' is not a function, it's a %T", methodcall->name, b->type); + return Match(b->type, FunctionType)->ret; + } else { + auto interface = Match(self_value_t, InterfaceType); + for (arg_t *field = interface->fields; field; field = field->next) { + if (streq(field->name, methodcall->name)) { + env_t tmp_env = *env; + tmp_env.types = new(table_t, .fallback=tmp_env.types); + Table$str_set(tmp_env.types, interface->type_parameter, self_value_t); + type_t *field_t = field->type; + if (field_t->tag == ClosureType) + field_t = Match(field_t, ClosureType)->fn; + if (field_t->tag != FunctionType) + code_err(ast, "'%s' is not a function, it's a %T", methodcall->name, b->type); + return Match(field_t, FunctionType)->ret; + } + } + code_err(ast, "There is no method called '%s' on the interface type %s", methodcall->name, interface->name); + } + } default: { type_t *fn_type_t = get_method_type(env, call->self, call->name); if (!fn_type_t) @@ -614,7 +674,7 @@ type_t *get_type(env_t *env, ast_t *ast) // Early out if the type is knowable without any context from the block: switch (last->ast->tag) { - case UpdateAssign: case Assign: case Declare: case FunctionDef: case StructDef: case EnumDef: case LangDef: + case UpdateAssign: case Assign: case Declare: case FunctionDef: case StructDef: case EnumDef: case LangDef: case InterfaceDef: return Type(VoidType); default: break; } @@ -791,7 +851,7 @@ type_t *get_type(env_t *env, ast_t *ast) return Type(ClosureType, Type(FunctionType, .args=args, .ret=ret)); } - case FunctionDef: case StructDef: case EnumDef: case LangDef: { + case FunctionDef: case StructDef: case EnumDef: case LangDef: case InterfaceDef: { return Type(VoidType); } @@ -930,7 +990,7 @@ type_t *get_type(env_t *env, ast_t *ast) bool is_discardable(env_t *env, ast_t *ast) { switch (ast->tag) { - case UpdateAssign: case Assign: case Declare: case FunctionDef: case StructDef: case EnumDef: case LangDef: case Use: + case UpdateAssign: case Assign: case Declare: case FunctionDef: case StructDef: case EnumDef: case LangDef: case InterfaceDef: case Use: return true; default: break; } @@ -45,6 +45,9 @@ CORD type_to_cord(type_t *t) { auto struct_ = Match(t, StructType); return struct_->name; } + case InterfaceType: { + return Match(t, InterfaceType)->name; + } case PointerType: { auto ptr = Match(t, PointerType); CORD sigil = ptr->is_stack ? "&" : "@"; @@ -213,6 +216,7 @@ bool has_heap_memory(type_t *t) } return false; } + case InterfaceType: return true; default: return false; } } @@ -289,6 +293,7 @@ bool can_leave_uninitialized(type_t *t) } return true; } + case InterfaceType: return false; default: return false; } } @@ -316,6 +321,13 @@ static bool _can_have_cycles(type_t *t, table_t *seen) } return false; } + case InterfaceType: { + for (arg_t *field = Match(t, InterfaceType)->fields; field; field = field->next) { + if (_can_have_cycles(field->type, seen)) + return true; + } + return false; + } default: return false; } } @@ -362,6 +374,13 @@ type_t *replace_type(type_t *t, type_t *target, type_t *replacement) Match((struct type_s*)t, EnumType)->tags = tags; return t; } + case InterfaceType: { + auto interface = Match(t, InterfaceType); + arg_t *fields = LIST_MAP(interface->fields, field, .type=replace_type(field->type, target, replacement)); + t = COPY(t); + Match((struct type_s*)t, InterfaceType)->fields = fields; + return t; + } default: return t; } #undef COPY @@ -382,9 +401,10 @@ size_t type_size(type_t *t) case FunctionType: return sizeof(void*); case ClosureType: return sizeof(struct {void *fn, *userdata;}); case PointerType: return sizeof(void*); - case StructType: { - size_t size = 0; - for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + case StructType: case InterfaceType: { + arg_t *fields = t->tag == StructType ? Match(t, StructType)->fields : Match(t, InterfaceType)->fields; + size_t size = t->tag == StructType ? 0 : sizeof(void*); + for (arg_t *field = fields; field; field = field->next) { type_t *field_type = field->type; if (field_type->tag == BoolType) { size += 1; // Bit packing @@ -432,9 +452,10 @@ size_t type_align(type_t *t) case FunctionType: return __alignof__(void*); case ClosureType: return __alignof__(struct {void *fn, *userdata;}); case PointerType: return __alignof__(void*); - case StructType: { - size_t align = 0; - for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { + case StructType: case InterfaceType: { + arg_t *fields = t->tag == StructType ? Match(t, StructType)->fields : Match(t, InterfaceType)->fields; + size_t align = t->tag == StructType ? 0 : sizeof(void*); + for (arg_t *field = fields; field; field = field->next) { size_t field_align = type_align(field->type); if (field_align > align) align = field_align; } @@ -466,6 +487,14 @@ type_t *get_field_type(type_t *t, const char *field_name) } return NULL; } + case InterfaceType: { + auto interface = Match(t, InterfaceType); + for (arg_t *field = interface->fields; field; field = field->next) { + if (streq(field->name, field_name)) + return field->type; + } + return NULL; + } case EnumType: { auto e = Match(t, EnumType); for (tag_t *tag = e->tags; tag; tag = tag->next) { @@ -52,6 +52,7 @@ struct type_s { PointerType, StructType, EnumType, + InterfaceType, TypeInfoType, ModuleType, } tag; @@ -99,6 +100,12 @@ struct type_s { struct env_s *env; } EnumType; struct { + const char *name, *type_parameter; + arg_t *fields; + bool opaque; + struct env_s *env; + } InterfaceType; + struct { const char *name; type_t *type; struct env_s *env; |
