diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-02-20 17:13:50 -0500 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-02-20 17:13:50 -0500 |
| commit | 91f66d80bb308b9aeca3b74b72e1593539c38862 (patch) | |
| tree | 8a017837c51dd71d1467ffffeaf9eb361e191291 | |
| parent | 43a2959be3835fac0b39ae1be3a9116b5ec184f0 (diff) | |
Support arbitrary argument constructors
| -rw-r--r-- | compile.c | 39 | ||||
| -rw-r--r-- | environment.c | 50 | ||||
| -rw-r--r-- | environment.h | 2 | ||||
| -rw-r--r-- | typecheck.c | 58 | ||||
| -rw-r--r-- | typecheck.h | 1 |
5 files changed, 115 insertions, 35 deletions
@@ -2681,7 +2681,7 @@ CORD compile(env_t *env, ast_t *ast) if (chunk->ast->tag == TextLiteral) { chunk_code = compile(env, chunk->ast); } else if (chunk_t->tag == TextType && streq(Match(chunk_t, TextType)->lang, lang)) { - binding_t *esc = get_lang_escape_function(env, lang, chunk_t); + binding_t *esc = get_constructor(env, text_t, new(arg_ast_t, .value=chunk->ast)); if (esc) { arg_t *arg_spec = Match(esc->type, FunctionType)->args; arg_ast_t *args = new(arg_ast_t, .value=chunk->ast); @@ -2690,7 +2690,7 @@ CORD compile(env_t *env, ast_t *ast) chunk_code = compile(env, chunk->ast); } } else if (lang) { - binding_t *esc = get_lang_escape_function(env, lang, chunk_t); + binding_t *esc = get_constructor(env, text_t, new(arg_ast_t, .value=chunk->ast)); if (!esc) code_err(chunk->ast, "I don't know how to convert %T to %T", chunk_t, text_t); @@ -3368,6 +3368,12 @@ CORD compile(env_t *env, ast_t *ast) } else if (fn_t->tag == TypeInfoType) { type_t *t = Match(fn_t, TypeInfoType)->type; + binding_t *constructor = get_constructor(env, t, call->args); + if (constructor) { + arg_t *arg_spec = Match(constructor->type, FunctionType)->args; + return CORD_all(constructor->code, "(", compile_arguments(env, ast, arg_spec, call->args), ")"); + } + type_t *actual = call->args ? get_type(env, call->args->value) : NULL; if (t->tag == StructType) { // Struct constructor: @@ -3430,25 +3436,14 @@ CORD compile(env_t *env, ast_t *ast) } else if (t->tag == TextType) { if (!call->args) code_err(ast, "This constructor needs a value"); const char *lang = Match(t, TextType)->lang; - if (lang) { // Escape for DSL - type_t *first_type = get_type(env, call->args->value); - if (type_eq(first_type, t)) - return compile(env, call->args->value); - - binding_t *esc = get_lang_escape_function(env, lang, first_type); - if (!esc) - code_err(ast, "I don't know how to convert %T to %T", first_type, t); - - arg_t *arg_spec = Match(esc->type, FunctionType)->args; - return CORD_all(esc->code, "(", compile_arguments(env, ast, arg_spec, call->args), ")"); - } else { - // Text constructor: - if (!call->args || call->args->next) - code_err(call->fn, "This constructor takes exactly 1 argument"); - if (type_eq(actual, t)) - return compile(env, call->args->value); - return expr_as_text(env, compile(env, call->args->value), actual, "no"); - } + if (lang) + code_err(call->fn, "I don't have a constructor defined for these arguments"); + // Text constructor: + if (!call->args || call->args->next) + code_err(call->fn, "This constructor takes exactly 1 argument"); + if (type_eq(actual, t)) + return compile(env, call->args->value); + return expr_as_text(env, compile(env, call->args->value), actual, "no"); } else if (t->tag == CStringType) { // C String constructor: if (!call->args || call->args->next) @@ -4489,7 +4484,7 @@ CORD compile_statement_namespace_header(env_t *env, ast_t *ast) CORD ret_type_code = compile_type(ret_t); CORD name = CORD_all(namespace_prefix(env, env->namespace), decl_name); if (env->namespace && env->namespace->parent && env->namespace->name && streq(decl_name, env->namespace->name)) - name = CORD_asprintf("%r$%ld", name, get_line_number(ast->file, ast->start)); + name = CORD_asprintf("%r%ld", namespace_prefix(env, env->namespace), get_line_number(ast->file, ast->start)); return CORD_all(ret_type_code, " ", name, arg_signature, ";\n"); } default: return CORD_EMPTY; diff --git a/environment.c b/environment.c index ce6fcbbf..44e74ed1 100644 --- a/environment.c +++ b/environment.c @@ -690,22 +690,50 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name) return NULL; } -PUREFUNC binding_t *get_lang_escape_function(env_t *env, const char *lang_name, type_t *type_to_escape) +PUREFUNC binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args) { - if (!lang_name) lang_name = "Text"; - binding_t *typeinfo = get_binding(env, lang_name); + const char *type_name; + t = value_type(t); + switch (t->tag) { + case TextType: { + type_name = Match(t, TextType)->lang; + if (type_name == NULL) type_name = "Text"; + break; + } + case StructType: { + type_name = Match(t, StructType)->name; + break; + } + case EnumType: { + type_name = Match(t, EnumType)->name; + break; + } + default: { + type_name = NULL; + break; + } + } + + if (!type_name) + return NULL; + + binding_t *typeinfo = get_binding(env, type_name); assert(typeinfo && typeinfo->type->tag == TypeInfoType); - env_t *lang_env = Match(typeinfo->type, TypeInfoType)->env; - Array_t constructors = lang_env->namespace->constructors; + env_t *type_env = Match(typeinfo->type, TypeInfoType)->env; + Array_t constructors = type_env->namespace->constructors; + // Prioritize exact matches: + for (int64_t i = 0; i < constructors.length; i++) { + binding_t *b = constructors.data + i*constructors.stride; + auto fn = Match(b->type, FunctionType); + if (is_valid_call(env, fn->args, args, false)) + return b; + } + // Fall back to promotion: for (int64_t i = 0; i < constructors.length; i++) { binding_t *b = constructors.data + i*constructors.stride; auto fn = Match(b->type, FunctionType); - if (!fn->args || fn->args->next) continue; - if (fn->ret->tag != TextType || !streq(Match(fn->ret, TextType)->lang, lang_name)) - continue; - if (!can_promote(type_to_escape, fn->args->type)) - continue; - return b; + if (is_valid_call(env, fn->args, args, true)) + return b; } return NULL; } diff --git a/environment.h b/environment.h index 97a6647d..c3d897b8 100644 --- a/environment.h +++ b/environment.h @@ -65,7 +65,7 @@ env_t *namespace_env(env_t *env, const char *namespace_name); __attribute__((format(printf, 4, 5))) _Noreturn void compiler_err(file_t *f, const char *start, const char *end, const char *fmt, ...); binding_t *get_binding(env_t *env, const char *name); -binding_t *get_lang_escape_function(env_t *env, const char *lang_name, type_t *type_to_escape); +binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args); void set_binding(env_t *env, const char *name, type_t *type, CORD code); binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); #define code_err(ast, ...) compiler_err((ast)->file, (ast)->start, (ast)->end, __VA_ARGS__) diff --git a/typecheck.c b/typecheck.c index cbdbc8cd..26a8ff9f 100644 --- a/typecheck.c +++ b/typecheck.c @@ -791,7 +791,11 @@ 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 == BigIntType || t->tag == NumType + + binding_t *constructor = get_constructor(env, t, call->args); + if (constructor) + return t; + else if (t->tag == StructType || t->tag == IntType || t->tag == BigIntType || t->tag == NumType || t->tag == ByteType || t->tag == TextType || t->tag == CStringType || t->tag == MomentType) return t; // Constructor code_err(call->fn, "This is not a type that has a constructor"); @@ -1363,6 +1367,58 @@ type_t *get_arg_type(env_t *env, arg_t *arg) return get_type(env, arg->default_val); } +bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, bool promotion_allowed) +{ + Table_t used_args = {}; + for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) { + type_t *spec_type = get_arg_type(env, spec_arg); + int64_t i = 1; + // Find keyword: + if (spec_arg->name) { + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (call_arg->name && streq(call_arg->name, spec_arg->name)) { + type_t *call_type = get_arg_ast_type(env, call_arg); + if (!(type_eq(call_type, spec_type) || (promotion_allowed && can_promote(call_type, spec_type)))) + return false; + Table$str_set(&used_args, call_arg->name, call_arg); + goto found_it; + } + } + } + // Find positional: + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (call_arg->name) continue; + const char *pseudoname = heap_strf("%ld", i++); + if (!Table$str_get(used_args, pseudoname)) { + type_t *call_type = get_arg_ast_type(env, call_arg); + if (!(type_eq(call_type, spec_type) || (promotion_allowed && can_promote(call_type, spec_type)))) + return false; + Table$str_set(&used_args, pseudoname, call_arg); + goto found_it; + } + } + + if (spec_arg->default_val) + goto found_it; + + return false; + found_it: continue; + } + + int64_t i = 1; + for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { + if (call_arg->name) { + if (!Table$str_get(used_args, call_arg->name)) + return false; + } else { + const char *pseudoname = heap_strf("%ld", i++); + if (!Table$str_get(used_args, pseudoname)) + return false; + } + } + return true; +} + PUREFUNC bool can_be_mutated(env_t *env, ast_t *ast) { switch (ast->tag) { diff --git a/typecheck.h b/typecheck.h index 1639e1e8..b3f1dd66 100644 --- a/typecheck.h +++ b/typecheck.h @@ -25,5 +25,6 @@ PUREFUNC bool can_be_mutated(env_t *env, ast_t *ast); type_t *parse_type_string(env_t *env, const char *str); type_t *get_method_type(env_t *env, ast_t *self, const char *name); PUREFUNC bool is_constant(env_t *env, ast_t *ast); +bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, bool promotion_allowed); // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 |
