Simplify enum/struct codegen by using reusable general-purpose

metamethods for structs/enums instead of metamethod codegen for each struct/enum
defined.
This commit is contained in:
Bruce Hill 2024-10-08 13:35:18 -04:00
parent 6b9055db7c
commit 954ed42934
7 changed files with 266 additions and 375 deletions

View File

@ -1461,9 +1461,7 @@ CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color)
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 OptionalType: return CORD_asprintf("Optional$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: return CORD_asprintf("generic_as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t));
default: compiler_err(NULL, NULL, NULL, "Stringifying is not supported for %T", t);
}
}
@ -1952,6 +1950,7 @@ CORD compile(env_t *env, ast_t *ast)
code_err(ast, "I don't know how to get the negative value of type %T", t);
}
// TODO: for constructors, do new(T, ...) instead of heap((T){...})
case HeapAllocate: return CORD_asprintf("heap(%r)", compile(env, Match(ast, HeapAllocate)->value));
case StackReference: {
ast_t *subject = Match(ast, StackReference)->value;
@ -3464,7 +3463,8 @@ CORD compile_type_info(env_t *env, type_t *t)
return CORD_asprintf("Closure$info(%r)", CORD_quoted(type_to_cord(t)));
}
case OptionalType: {
return CORD_asprintf("Optional$info(%r)", compile_type_info(env, Match(t, OptionalType)->type));
type_t *non_optional = Match(t, OptionalType)->type;
return CORD_asprintf("Optional$info(%zu, %zu, %r)", type_size(t), type_align(t), compile_type_info(env, non_optional));
}
case TypeInfoType: return CORD_all("TypeInfo$info(", CORD_quoted(type_to_cord(Match(t, TypeInfoType)->type)), ")");
case MemoryType: return "&Memory$info";
@ -3656,9 +3656,9 @@ CORD compile_file(env_t *env, ast_t *ast)
"#include <tomo/tomo.h>\n"
"#include \"", name, ".tm.h\"\n\n",
env->code->local_typedefs, "\n",
env->code->typeinfos, "\n",
env->code->staticdefs, "\n",
env->code->funcs, "\n",
env->code->typeinfos, "\n",
"public void $", env->namespace->name, "$$initialize(void) {\n",
"static bool initialized = false;\n",
"if (initialized) return;\n",

253
enums.c
View File

@ -13,193 +13,53 @@
#include "typecheck.h"
#include "stdlib/util.h"
PUREFUNC static bool has_extra_data(tag_ast_t *tags)
{
for (tag_ast_t *tag = tags; tag; tag = tag->next) {
if (tag->fields) return true;
}
return false;
}
static CORD compile_str_method(env_t *env, ast_t *ast)
{
auto def = Match(ast, EnumDef);
CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name);
CORD str_func = CORD_all("static Text_t ", full_name, "$as_text(", full_name, "_t *obj, bool use_color) {\n"
"\tif (!obj) return Text(\"", def->name, "\");\n"
"switch (obj->tag) {\n");
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
if (!tag->fields) {
str_func = CORD_all(str_func, "\tcase ", full_name, "$tag$", tag->name, ": return use_color ? Text(\"\\x1b[36;1m",
def->name, ".", tag->name, "\\x1b[m\") : Text(\"", def->name, ".", tag->name, "\");\n");
continue;
}
str_func = CORD_all(str_func, "\tcase ", full_name, "$tag$", tag->name, ": return Text$concat(use_color ? Text(\"\\x1b[36;1m",
def->name, ".", tag->name, "\\x1b[m(\") : Text(\"", def->name, ".", tag->name, "(\")");
if (tag->secret) {
str_func = CORD_cat(str_func, ", use_color ? Text(\"\\x1b[2m...\\x1b[m\") : Text(\"...\", \")\"));\n");
continue;
}
if (tag->fields && !tag->fields->next) { // Single-member tags don't need to print member names:
type_t *field_t = get_arg_ast_type(env, tag->fields);
CORD field_str = expr_as_text(env, CORD_all("obj->$", tag->name, ".$", tag->fields->name), field_t, "use_color");
str_func = CORD_all(str_func, ", ", field_str);
} else {
for (arg_ast_t *field = tag->fields; field; field = field->next) {
type_t *field_t = get_arg_ast_type(env, field);
CORD field_str = expr_as_text(env, CORD_all("obj->$", tag->name, ".$", field->name), field_t, "use_color");
str_func = CORD_all(str_func, ", Text(\"", field->name, "=\"), ", field_str);
if (field->next) str_func = CORD_cat(str_func, ", Text(\", \")");
}
}
str_func = CORD_cat(str_func, ", Text(\")\"));\n");
}
str_func = CORD_cat(str_func, "\tdefault: return (Text_t){.length=0};\n\t}\n}\n");
return str_func;
}
static CORD compile_compare_method(env_t *env, ast_t *ast)
{
auto def = Match(ast, EnumDef);
CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name);
if (!has_extra_data(def->tags)) {
// Comparisons are simpler if there is only a tag, no tagged data:
return CORD_all("static int ", full_name, "$compare(const ", full_name, "_t *x, const ", full_name,
"_t *y, const TypeInfo_t *info) {\n"
"(void)info;\n"
"return (x->tag - y->tag);\n"
"}\n");
}
CORD cmp_func = CORD_all("static int ", full_name, "$compare(const ", full_name, "_t *x, const ", full_name,
"_t *y, const TypeInfo_t *info) {\n"
"(void)info;\n"
"int diff = x->tag - y->tag;\n"
"if (diff) return diff;\n"
"switch (x->tag) {\n");
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
if (tag->fields) {
type_t *tag_type = Table$str_get(*env->types, CORD_to_const_char_star(CORD_all(def->name, "$", tag->name)));
cmp_func = CORD_all(cmp_func, "\tcase ", full_name, "$tag$", tag->name, ": "
"return generic_compare(&x->$", tag->name, ", &y->$", tag->name, ", ", compile_type_info(env, tag_type), ");\n");
} else {
cmp_func = CORD_all(cmp_func, "\tcase ", full_name, "$tag$", tag->name, ": return 0;\n");
}
}
cmp_func = CORD_all(cmp_func, "default: return 0;\n}\n}\n");
return cmp_func;
}
static CORD compile_equals_method(env_t *env, ast_t *ast)
{
auto def = Match(ast, EnumDef);
CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name);
if (!has_extra_data(def->tags)) {
// Equality is simpler if there is only a tag, no tagged data:
return CORD_all("static bool ", full_name, "$equal(const ", full_name, "_t *x, const ", full_name,
"_t *y, const TypeInfo_t *info) {\n"
"(void)info;\n"
"return (x->tag == y->tag);\n"
"}\n");
}
CORD eq_func = CORD_all("static bool ", full_name, "$equal(const ", full_name, "_t *x, const ", full_name,
"_t *y, const TypeInfo_t *info) {\n"
"(void)info;\n"
"if (x->tag != y->tag) return no;\n"
"switch (x->tag) {\n");
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
if (tag->fields) {
type_t *tag_type = Table$str_get(*env->types, CORD_to_const_char_star(CORD_all(def->name, "$", tag->name)));
eq_func = CORD_all(eq_func, "\tcase ", full_name, "$tag$", tag->name, ": "
"return generic_equal(&x->$", tag->name, ", &y->$", tag->name, ", ", compile_type_info(env, tag_type), ");\n");
} else {
eq_func = CORD_all(eq_func, "\tcase ", full_name, "$tag$", tag->name, ": return yes;\n");
}
}
eq_func = CORD_all(eq_func, "default: return 0;\n}\n}\n");
return eq_func;
}
static CORD compile_hash_method(env_t *env, ast_t *ast)
{
auto def = Match(ast, EnumDef);
CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name);
if (!has_extra_data(def->tags)) {
// Hashing is simpler if there is only a tag, no tagged data:
return CORD_all("static uint64_t ", full_name, "$hash(const ", full_name, "_t *obj, const TypeInfo_t *info) {\n"
"(void)info;\n"
"return siphash24((void*)&obj->tag, sizeof(obj->tag));\n"
"\n}\n");
}
CORD hash_func = CORD_all("static uint64_t ", full_name, "$hash(const ", full_name, "_t *obj, const TypeInfo_t *info) {\n"
"(void)info;\n"
"uint64_t hashes[2] = {(uint64_t)obj->tag, 0};\n"
"switch (obj->tag) {\n");
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
if (tag->fields) {
type_t *tag_type = Table$str_get(*env->types, CORD_to_const_char_star(CORD_all(def->name, "$", tag->name)));
hash_func = CORD_all(hash_func, "\tcase ", full_name, "$tag$", tag->name, ": "
"hashes[1] = generic_hash(&obj->$", tag->name, ", ", compile_type_info(env, tag_type), ");\n"
"break;\n");
} else {
hash_func = CORD_all(hash_func, "\tcase ", full_name, "$tag$", tag->name, ": break;\n");
}
}
hash_func = CORD_all(hash_func, "}\n"
"return siphash24((void*)&hashes, sizeof(hashes));\n}\n");
return hash_func;
}
void compile_enum_def(env_t *env, ast_t *ast)
{
auto def = Match(ast, EnumDef);
CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name);
// Compile member types and constructors:
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
compile_struct_def(env, WrapAST(ast, StructDef, .name=CORD_to_const_char_star(CORD_all(def->name, "$", tag->name)), .fields=tag->fields));
if (tag->fields) { // Constructor macros:
CORD arg_sig = CORD_EMPTY;
for (arg_ast_t *field = tag->fields; field; field = field->next) {
type_t *field_t = get_arg_ast_type(env, field);
arg_sig = CORD_all(arg_sig, compile_declaration(field_t, CORD_all("$", field->name)));
if (field->next) arg_sig = CORD_cat(arg_sig, ", ");
}
if (arg_sig == CORD_EMPTY) arg_sig = "void";
CORD constructor_impl = CORD_all("public inline ", full_name, "_t ", full_name, "$tagged$", tag->name, "(", arg_sig, ") { return (",
full_name, "_t){.tag=", full_name, "$tag$", tag->name, ", .$", tag->name, "={");
for (arg_ast_t *field = tag->fields; field; field = field->next) {
constructor_impl = CORD_all(constructor_impl, "$", field->name);
if (field->next) constructor_impl = CORD_cat(constructor_impl, ", ");
}
constructor_impl = CORD_cat(constructor_impl, "}}; }\n");
env->code->funcs = CORD_cat(env->code->funcs, constructor_impl);
if (!tag->fields) continue;
const char *tag_name = heap_strf("%s$%s", def->name, tag->name);
compile_struct_def(env, WrapAST(ast, StructDef, .name=tag_name, .fields=tag->fields, .secret=tag->secret));
CORD arg_sig = CORD_EMPTY;
for (arg_ast_t *field = tag->fields; field; field = field->next) {
type_t *field_t = get_arg_ast_type(env, field);
arg_sig = CORD_all(arg_sig, compile_declaration(field_t, CORD_all("$", field->name)));
if (field->next) arg_sig = CORD_cat(arg_sig, ", ");
}
if (arg_sig == CORD_EMPTY) arg_sig = "void";
CORD constructor_impl = CORD_all("public inline ", full_name, "_t ", full_name, "$tagged$", tag->name, "(", arg_sig, ") { return (",
full_name, "_t){.tag=", full_name, "$tag$", tag->name, ", .$", tag->name, "={");
for (arg_ast_t *field = tag->fields; field; field = field->next) {
constructor_impl = CORD_all(constructor_impl, "$", field->name);
if (field->next) constructor_impl = CORD_cat(constructor_impl, ", ");
}
constructor_impl = CORD_cat(constructor_impl, "}}; }\n");
env->code->funcs = CORD_cat(env->code->funcs, constructor_impl);
}
int num_tags = 0;
for (tag_ast_t *t = def->tags; t; t = t->next)
num_tags += 1;
type_t *t = Table$str_get(*env->types, def->name);
CORD typeinfo = CORD_asprintf("public const TypeInfo_t %s = {%zu, %zu, {.tag=EnumInfo, .CustomInfo={",
full_name, type_size(t), type_align(t));
CORD typeinfo = CORD_asprintf("public const TypeInfo_t %r = {%zu, %zu, {.tag=EnumInfo, .EnumInfo={.name=\"%s\", "
".num_tags=%d, .tags=(NamedType_t[]){",
full_name, type_size(t), type_align(t), def->name, num_tags);
env->code->funcs = CORD_all(env->code->funcs, compile_str_method(env, ast));
if (has_extra_data(def->tags)) {
env->code->funcs = CORD_all(
env->code->funcs,
compile_equals_method(env, ast), compile_compare_method(env, ast),
compile_hash_method(env, ast));
typeinfo = CORD_all(
typeinfo,
".as_text=(void*)", full_name, "$as_text, "
".equal=(void*)", full_name, "$equal, "
".hash=(void*)", full_name, "$hash, "
".compare=(void*)", full_name, "$compare");
} else {
// No need for custom methods if there is no tagged data
typeinfo = CORD_all(typeinfo, ".as_text=(void*)", full_name, "$as_text");
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
const char *tag_type_name = heap_strf("%s$%s", def->name, tag->name);
type_t *tag_type = Table$str_get(*env->types, tag_type_name);
if (tag_type && Match(tag_type, StructType)->fields)
typeinfo = CORD_all(typeinfo, "{\"", tag->name, "\", ", compile_type_info(env, tag_type), "}, ");
else
typeinfo = CORD_all(typeinfo, "{\"", tag->name, "\"}, ");
}
typeinfo = CORD_cat(typeinfo, "}}};\n");
typeinfo = CORD_all(typeinfo, "}}}};\n");
env->code->typeinfos = CORD_all(env->code->typeinfos, typeinfo);
compile_namespace(env, def->name, def->namespace);
}
@ -211,35 +71,40 @@ CORD compile_enum_header(env_t *env, ast_t *ast)
CORD enum_def = CORD_all("struct ", full_name, "_s {\n"
"\tenum { ", full_name, "$null=0, ");
bool has_any_tags_with_fields = false;
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
enum_def = CORD_all(enum_def, full_name, "$tag$", tag->name);
if (tag->next) enum_def = CORD_all(enum_def, ", ");
has_any_tags_with_fields = has_any_tags_with_fields || (tag->fields != NULL);
}
enum_def = CORD_all(enum_def, "} tag;\n"
"union {\n");
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
CORD field_def = compile_struct_header(env, WrapAST(ast, StructDef, .name=CORD_to_const_char_star(CORD_all(def->name, "$", tag->name)), .fields=tag->fields));
all_defs = CORD_all(all_defs, field_def);
enum_def = CORD_all(enum_def, full_name, "$", tag->name, "_t $", tag->name, ";\n");
enum_def = CORD_all(enum_def, "} tag;\n");
if (has_any_tags_with_fields) {
enum_def = CORD_all(enum_def, "union {\n");
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
if (!tag->fields) continue;
CORD field_def = compile_struct_header(env, WrapAST(ast, StructDef, .name=CORD_to_const_char_star(CORD_all(def->name, "$", tag->name)), .fields=tag->fields));
all_defs = CORD_all(all_defs, field_def);
enum_def = CORD_all(enum_def, full_name, "$", tag->name, "_t $", tag->name, ";\n");
}
enum_def = CORD_all(enum_def, "};\n");
}
enum_def = CORD_all(enum_def, "};\n};\n");
enum_def = CORD_all(enum_def, "};\n");
all_defs = CORD_all(all_defs, enum_def);
all_defs = CORD_all(all_defs, "extern const TypeInfo_t ", full_name, ";\n");
for (tag_ast_t *tag = def->tags; tag; tag = tag->next) {
all_defs = CORD_all(all_defs,
"extern const TypeInfo_t ", namespace_prefix(env, env->namespace), def->name, "$", tag->name, ";\n");
if (tag->fields) { // Constructor macros:
CORD arg_sig = CORD_EMPTY;
for (arg_ast_t *field = tag->fields; field; field = field->next) {
type_t *field_t = get_arg_ast_type(env, field);
arg_sig = CORD_all(arg_sig, compile_declaration(field_t, CORD_all("$", field->name)));
if (field->next) arg_sig = CORD_all(arg_sig, ", ");
}
if (arg_sig == CORD_EMPTY) arg_sig = "void";
CORD constructor_def = CORD_all(full_name, "_t ", full_name, "$tagged$", tag->name, "(", arg_sig, ");\n");
all_defs = CORD_all(all_defs, constructor_def);
if (!tag->fields) continue;
CORD arg_sig = CORD_EMPTY;
for (arg_ast_t *field = tag->fields; field; field = field->next) {
type_t *field_t = get_arg_ast_type(env, field);
arg_sig = CORD_all(arg_sig, compile_declaration(field_t, CORD_all("$", field->name)));
if (field->next) arg_sig = CORD_all(arg_sig, ", ");
}
if (arg_sig == CORD_EMPTY) arg_sig = "void";
CORD constructor_def = CORD_all(full_name, "_t ", full_name, "$tagged$", tag->name, "(", arg_sig, ");\n");
all_defs = CORD_all(all_defs, constructor_def);
}
return CORD_all(all_defs, compile_namespace_header(env, def->name, def->namespace));
}

View File

@ -4,6 +4,7 @@
#include <string.h>
#include "arrays.h"
#include "bools.h"
#include "channels.h"
#include "functiontype.h"
#include "metamethods.h"
@ -15,6 +16,7 @@
#include "util.h"
#pragma GCC diagnostic ignored "-Wstack-protector"
PUREFUNC public uint64_t generic_hash(const void *obj, const TypeInfo_t *type)
{
switch (type->tag) {
@ -23,8 +25,54 @@ PUREFUNC public uint64_t generic_hash(const void *obj, const TypeInfo_t *type)
case ChannelInfo: return Channel$hash((Channel_t**)obj, type);
case TableInfo: return Table$hash(obj, type);
case OptionalInfo: return is_null(obj, type->OptionalInfo.type) ? 0 : generic_hash(obj, type->OptionalInfo.type);
case EmptyStructInfo: return 0;
case CustomInfo: case StructInfo: case EnumInfo: case CStringInfo: // These all share the same info
case StructInfo: {
if (type->StructInfo.num_fields == 0) {
return 0;
} else if (type->StructInfo.num_fields == 1) {
return generic_hash(obj, type->StructInfo.fields[0].type);
} else {
uint32_t field_hashes[type->StructInfo.num_fields] = {};
ptrdiff_t byte_offset = 0;
ptrdiff_t bit_offset = 0;
for (int i = 0; i < type->StructInfo.num_fields; i++) {
NamedType_t field = type->StructInfo.fields[i];
if (field.type == &Bool$info) {
bool b = ((*(char*)(obj + byte_offset)) >> bit_offset) & 0x1;
field_hashes[i] = (uint32_t)b;
bit_offset += 1;
if (bit_offset >= 8) {
byte_offset += 1;
bit_offset = 0;
}
} else {
if (bit_offset > 0) {
byte_offset += 1;
bit_offset = 0;
}
if (field.type->align && byte_offset % field.type->align > 0)
byte_offset += field.type->align - (byte_offset % field.type->align);
field_hashes[i] = generic_hash(obj + byte_offset, field.type);
byte_offset += field.type->size;
}
}
return siphash24((void*)field_hashes, sizeof(field_hashes));
}
}
case EnumInfo: {
int32_t tag = *(int32_t*)obj;
uint32_t components[2] = {(uint32_t)tag, 0};
const TypeInfo_t *value = type->EnumInfo.tags[tag-1].type;
if (value && value->size > 0) {
ptrdiff_t byte_offset = sizeof(int32_t);
if (value->align && byte_offset % value->align > 0)
byte_offset += value->align - (byte_offset % value->align);
components[1] = generic_hash(obj + byte_offset, value);
}
return siphash24((void*)components, sizeof(components));
}
case CustomInfo: case CStringInfo: // These all share the same info
if (!type->CustomInfo.hash)
goto hash_data;
return type->CustomInfo.hash(obj, type);
@ -52,8 +100,52 @@ PUREFUNC public int32_t generic_compare(const void *x, const void *y, const Type
else if (x_is_null != y_is_null) return (int32_t)y_is_null - (int32_t)x_is_null;
else return generic_compare(x, y, type->OptionalInfo.type);
}
case EmptyStructInfo: return 0;
case CustomInfo: case StructInfo: case EnumInfo: case CStringInfo: // These all share the same info
case StructInfo: {
ptrdiff_t byte_offset = 0;
ptrdiff_t bit_offset = 0;
for (int i = 0; i < type->StructInfo.num_fields; i++) {
NamedType_t field = type->StructInfo.fields[i];
if (field.type == &Bool$info) {
bool bx = ((*(char*)(x + byte_offset)) >> bit_offset) & 0x1;
bool by = ((*(char*)(y + byte_offset)) >> bit_offset) & 0x1;
if (bx != by)
return (int32_t)bx - (int32_t)by;
bit_offset += 1;
if (bit_offset >= 8) {
byte_offset += 1;
bit_offset = 0;
}
} else {
if (bit_offset > 0) {
byte_offset += 1;
bit_offset = 0;
}
if (field.type->align && byte_offset % field.type->align > 0)
byte_offset += field.type->align - (byte_offset % field.type->align);
int32_t cmp = generic_compare(x + byte_offset, y + byte_offset, field.type);
if (cmp != 0)
return cmp;
byte_offset += field.type->size;
}
}
return 0;
}
case EnumInfo: {
int32_t x_tag = *(int32_t*)x;
int32_t y_tag = *(int32_t*)y;
if (x_tag != y_tag)
return x_tag > y_tag ? 1 : -1;
const TypeInfo_t *value = type->EnumInfo.tags[x_tag-1].type;
if (value && value->size > 0) {
ptrdiff_t byte_offset = sizeof(int32_t);
if (value->align && byte_offset % value->align > 0)
byte_offset += value->align - (byte_offset % value->align);
return generic_compare(x + byte_offset, y + byte_offset, value);
}
return 0;
}
case CustomInfo: case CStringInfo: // These all share the same info
if (!type->CustomInfo.compare)
goto compare_data;
return type->CustomInfo.compare(x, y, type);
@ -73,7 +165,6 @@ PUREFUNC public bool generic_equal(const void *x, const void *y, const TypeInfo_
case ArrayInfo: return Array$equal(x, y, type);
case ChannelInfo: return Channel$equal((Channel_t**)x, (Channel_t**)y, type);
case TableInfo: return Table$equal(x, y, type);
case EmptyStructInfo: return true;
case OptionalInfo: {
bool x_is_null = is_null(x, type->OptionalInfo.type);
bool y_is_null = is_null(y, type->OptionalInfo.type);
@ -81,7 +172,9 @@ PUREFUNC public bool generic_equal(const void *x, const void *y, const TypeInfo_
else if (x_is_null != y_is_null) return false;
else return generic_equal(x, y, type->OptionalInfo.type);
}
case CustomInfo: case StructInfo: case EnumInfo: case CStringInfo: // These all share the same info
case StructInfo: case EnumInfo:
return (generic_compare(x, y, type) == 0);
case CustomInfo: case CStringInfo: // These all share the same info
if (!type->CustomInfo.equal)
goto use_generic_compare;
return type->CustomInfo.equal(x, y, type);
@ -102,15 +195,64 @@ public Text_t generic_as_text(const void *obj, bool colorize, const TypeInfo_t *
case TableInfo: return Table$as_text(obj, colorize, type);
case TypeInfoInfo: return Type$as_text(obj, colorize, type);
case OptionalInfo: return Optional$as_text(obj, colorize, type);
case EmptyStructInfo: return colorize ?
Text$concat(Text("\x1b[0;1m"), Text$from_str(type->EmptyStructInfo.name), Text("\x1b[m()"))
: Text$concat(Text$from_str(type->EmptyStructInfo.name), Text("()"));
case CustomInfo: case StructInfo: case EnumInfo: case CStringInfo: // These all share the same info
case StructInfo: {
if (!obj) return Text$from_str(type->StructInfo.name);
if (type->StructInfo.is_secret)
return Text$format(colorize ? "\x1b[0;1m%s\x1b[m(...)" : "%s(...)", type->StructInfo.name);
Text_t text = Text$format(colorize ? "\x1b[0;1m%s\x1b[m(" : "%s(", type->StructInfo.name);
ptrdiff_t byte_offset = 0;
ptrdiff_t bit_offset = 0;
for (int i = 0; i < type->StructInfo.num_fields; i++) {
NamedType_t field = type->StructInfo.fields[i];
if (i > 0)
text = Text$concat(text, Text(", "));
if (type->StructInfo.num_fields > 1)
text = Text$concat(text, Text$from_str(field.name), Text("="));
if (field.type == &Bool$info) {
bool b = ((*(char*)(obj + byte_offset)) >> bit_offset) & 0x1;
text = Text$concat(text, Text$from_str(colorize ? (b ? "\x1b[35myes\x1b[m" : "\x1b[35mno\x1b[m") : (b ? "yes" : "no")));
bit_offset += 1;
if (bit_offset >= 8) {
byte_offset += 1;
bit_offset = 0;
}
} else {
if (bit_offset > 0) {
byte_offset += 1;
bit_offset = 0;
}
if (field.type->align && byte_offset % field.type->align > 0)
byte_offset += field.type->align - (byte_offset % field.type->align);
text = Text$concat(text, generic_as_text(obj + byte_offset, colorize, field.type));
byte_offset += field.type->size;
}
}
return Text$concat(text, Text(")"));
}
case EnumInfo: {
if (!obj) return Text$from_str(type->EnumInfo.name);
int32_t tag = *(int32_t*)obj;
NamedType_t value = type->EnumInfo.tags[tag-1];
if (!value.type || value.type->size == 0)
return Text$format(colorize ? "\x1b[36;1m%s\x1b[m.\x1b[1m%s\x1b[m" : "%s.%s", type->EnumInfo.name, value.name);
ptrdiff_t byte_offset = sizeof(int32_t);
if (value.type->align && byte_offset % value.type->align > 0)
byte_offset += value.type->align - (byte_offset % value.type->align);
return Text$concat(Text$format(colorize ? "\x1b[36;1m%s\x1b[m." : "%s.", type->EnumInfo.name),
generic_as_text(obj + byte_offset, colorize, value.type));
}
case CustomInfo: case CStringInfo: // These all share the same info
if (!type->CustomInfo.as_text)
fail("No text function provided for type!\n");
return type->CustomInfo.as_text(obj, colorize, type);
case OpaqueInfo: return Text("???");
default: errx(1, "Invalid type tag: %d", type->tag);
default: fail("Invalid type tag: %d", type->tag);
}
}

View File

@ -25,7 +25,7 @@ public Text_t Type$as_text(const void *typeinfo, bool colorize, const TypeInfo_t
return Text$from_str(type->TypeInfoInfo.type_str);
}
public const TypeInfo_t Void$info = {.size=0, .align=0, .tag=EmptyStructInfo};
public const TypeInfo_t Abort$info = {.size=0, .align=0, .tag=EmptyStructInfo};
public const TypeInfo_t Void$info = {.size=0, .align=0, .tag=StructInfo};
public const TypeInfo_t Abort$info = {.size=0, .align=0, .tag=StructInfo};
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -7,18 +7,23 @@
#include "datatypes.h"
struct TypeInfo_t;
typedef struct TypeInfo_s TypeInfo_t;
typedef uint64_t (*hash_fn_t)(const void*, const struct TypeInfo_t*);
typedef int32_t (*compare_fn_t)(const void*, const void*, const struct TypeInfo_t*);
typedef bool (*equal_fn_t)(const void*, const void*, const struct TypeInfo_t*);
typedef Text_t (*text_fn_t)(const void*, bool, const struct TypeInfo_t*);
typedef uint64_t (*hash_fn_t)(const void*, const TypeInfo_t*);
typedef int32_t (*compare_fn_t)(const void*, const void*, const TypeInfo_t*);
typedef bool (*equal_fn_t)(const void*, const void*, const TypeInfo_t*);
typedef Text_t (*text_fn_t)(const void*, bool, const TypeInfo_t*);
typedef struct TypeInfo_t {
typedef struct {
const char *name;
const TypeInfo_t *type;
} NamedType_t;
struct TypeInfo_s {
int64_t size, align;
struct { // Anonymous tagged union for convenience
enum { CustomInfo, StructInfo, EnumInfo, PointerInfo, TextInfo, ArrayInfo, ChannelInfo, TableInfo, FunctionInfo,
OptionalInfo, TypeInfoInfo, OpaqueInfo, EmptyStructInfo, CStringInfo } tag;
enum { Invalid, CustomInfo, StructInfo, EnumInfo, PointerInfo, TextInfo, ArrayInfo, ChannelInfo, TableInfo, FunctionInfo,
OptionalInfo, TypeInfoInfo, OpaqueInfo, CStringInfo } tag;
union {
struct {
equal_fn_t equal;
@ -28,16 +33,16 @@ typedef struct TypeInfo_t {
} CustomInfo;
struct {
const char *sigil;
const struct TypeInfo_t *pointed;
const TypeInfo_t *pointed;
} PointerInfo;
struct {
const char *lang;
} TextInfo;
struct {
const struct TypeInfo_t *item;
const TypeInfo_t *item;
} ArrayInfo, ChannelInfo;
struct {
const struct TypeInfo_t *key, *value;
const TypeInfo_t *key, *value;
} TableInfo;
struct {
const char *type_str;
@ -46,16 +51,24 @@ typedef struct TypeInfo_t {
const char *type_str;
} TypeInfoInfo;
struct {
const struct TypeInfo_t *type;
const TypeInfo_t *type;
} OptionalInfo;
#pragma GCC diagnostic ignored "-Wpedantic"
struct {} OpaqueInfo;
struct {
const char *name;
} EmptyStructInfo;
int num_tags;
NamedType_t *tags;
} EnumInfo;
struct {
const char *name;
int num_fields;
bool is_secret:1;
NamedType_t *fields;
} StructInfo;
};
};
} TypeInfo_t;
};
#define Pointer$info(sigil_expr, pointed_info) &((TypeInfo_t){.size=sizeof(void*), .align=__alignof__(void*), \
.tag=PointerInfo, .PointerInfo={.sigil=sigil_expr, .pointed=pointed_info}})
@ -73,7 +86,7 @@ typedef struct TypeInfo_t {
.tag=FunctionInfo, .FunctionInfo.type_str=typestr})
#define Type$info(typestr) &((TypeInfo_t){.size=sizeof(TypeInfo_t), .align=__alignof__(TypeInfo_t), \
.tag=TypeInfoInfo, .TypeInfoInfo.type_str=typestr})
#define Optional$info(t) &((TypeInfo_t){.size=(t)->size, .align=(t)->align, \
#define Optional$info(_size, _align, t) &((TypeInfo_t){.size=_size, .align=_align, \
.tag=OptionalInfo, .OptionalInfo.type=t})
extern const TypeInfo_t Void$info;

163
structs.c
View File

@ -12,112 +12,6 @@
#include "typecheck.h"
#include "stdlib/util.h"
static CORD compile_str_method(env_t *env, ast_t *ast)
{
auto def = Match(ast, StructDef);
CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name);
const char *name = def->name;
const char *dollar = strrchr(name, '$');
if (dollar) name = dollar + 1;
CORD str_func = CORD_asprintf("static Text_t %r$as_text(%r_t *obj, bool use_color) {\n"
"\tif (!obj) return Text(\"%s\");\n", full_name, full_name, name);
if (def->secret) {
CORD_appendf(&str_func, "\treturn use_color ? Text(\"\\x1b[0;1m%s\\x1b[m(\\x1b[2m...\\x1b[m)\") : Text(\"%s(...)\");\n}",
name, name);
} else if (def->fields && !def->fields->next) { // Single-member structs don't need to print names:
type_t *field_type = get_arg_ast_type(env, def->fields);
CORD field_str = expr_as_text(env, CORD_cat("obj->$", def->fields->name), field_type, "use_color");
str_func = CORD_all(str_func, "\treturn Text$concat(use_color ? Text(\"\\x1b[0;1m", name, "\\x1b[m(\") : Text(\"", name, "(\"), ",
field_str, ", Text(\")\"));\n}\n");
} else {
CORD_appendf(&str_func, "\treturn Text$concat(use_color ? Text(\"\\x1b[0;1m%s\\x1b[m(\") : Text(\"%s(\")", name, name);
for (arg_ast_t *field = def->fields; field; field = field->next) {
type_t *field_type = get_arg_ast_type(env, field);
CORD field_str = expr_as_text(env, CORD_cat("obj->$", field->name), field_type, "use_color");
CORD_appendf(&str_func, ", Text(\"%s=\"), %r", field->name, field_str);
if (field->next) CORD_appendf(&str_func, ", Text(\", \")");
}
CORD_appendf(&str_func, ", Text(\")\"));\n}\n");
}
return str_func;
}
static CORD compile_compare_method(env_t *env, ast_t *ast)
{
auto def = Match(ast, StructDef);
CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name);
CORD cmp_func = CORD_all("static int ", full_name, "$compare(const ", full_name, "_t *x, const ", full_name,
"_t *y, const TypeInfo_t *info) {\n"
"(void)info;\n",
"int diff;\n");
for (arg_ast_t *field = def->fields; field; field = field->next) {
type_t *field_type = get_arg_ast_type(env, field);
switch (field_type->tag) {
case BigIntType:
cmp_func = CORD_all(cmp_func, "diff = Int$compare_value(x->$", field->name, ", y->$", field->name, ");");
break;
case BoolType: case IntType: case NumType: case PointerType: case FunctionType:
cmp_func = CORD_all(cmp_func, "diff = (x->$", field->name, " > y->$", field->name, ") - (x->$", field->name, " < y->$", field->name, ");");
break;
default:
cmp_func = CORD_all(cmp_func, "diff = generic_compare(&x->$", field->name, ", &y->$", field->name, ", ",
compile_type_info(env, field_type), ");\n");
break;
}
cmp_func = CORD_all(cmp_func, "if (diff != 0) return diff;\n");
}
cmp_func = CORD_all(cmp_func, "return 0;\n}\n");
return cmp_func;
}
static CORD compile_equals_method(env_t *env, ast_t *ast)
{
auto def = Match(ast, StructDef);
CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name);
CORD eq_func = CORD_all("static bool ", full_name, "$equal(const ", full_name, "_t *x, const ", full_name,
"_t *y, const TypeInfo_t *info) {\n"
"(void)info;\n");
CORD condition = CORD_EMPTY;
for (arg_ast_t *field = def->fields; field; field = field->next) {
if (condition != CORD_EMPTY)
condition = CORD_all(condition, " && ");
type_t *field_type = get_arg_ast_type(env, field);
switch (field_type->tag) {
case BigIntType:
condition = CORD_all(condition, "Int$equal_value(x->$", field->name, ", y->$", field->name, ")");
break;
case BoolType: case IntType: case NumType: case PointerType: case FunctionType:
condition = CORD_all(condition, "(x->$", field->name, " == y->$", field->name, ")");
break;
default:
condition = CORD_all(condition, "generic_equal(&x->$", field->name, ", &y->$", field->name, ", ",
compile_type_info(env, field_type), ")");
break;
}
}
eq_func = CORD_all(eq_func, "return ", condition, ";\n}\n");
return eq_func;
}
static CORD compile_hash_method(env_t *env, ast_t *ast)
{
auto def = Match(ast, StructDef);
CORD full_name = CORD_cat(namespace_prefix(env, env->namespace), def->name);
CORD hash_func = CORD_all("static uint64_t ", full_name, "$hash(const ", full_name, "_t *obj, const TypeInfo_t *info) {\n"
"(void)info;\n"
"uint64_t field_hashes[] = {");
for (arg_ast_t *field = def->fields; field; field = field->next) {
type_t *field_type = get_arg_ast_type(env, field);
if (field_type->tag == BoolType) // Bools can be bit fields, so you can't use *obj->field there:
hash_func = CORD_all(hash_func, "\n(uint32_t)(obj->$", field->name, "),");
else
hash_func = CORD_all(hash_func, "\ngeneric_hash(&obj->$", field->name, ", ", compile_type_info(env, field_type), "),");
}
hash_func = CORD_all(hash_func, "};\n"
"return siphash24((void*)&field_hashes, sizeof(field_hashes));\n}\n");
return hash_func;
}
void compile_struct_def(env_t *env, ast_t *ast)
{
auto def = Match(ast, StructDef);
@ -125,48 +19,25 @@ void compile_struct_def(env_t *env, ast_t *ast)
type_t *t = Table$str_get(*env->types, def->name);
assert(t && t->tag == StructType);
auto struct_ = Match(t, StructType);
if (def->fields) {
CORD typeinfo = CORD_asprintf("public const TypeInfo_t %r = {%zu, %zu, {.tag=StructInfo, .CustomInfo={",
full_name, type_size(t), type_align(t));
int num_fields = 0;
for (arg_ast_t *f = def->fields; f; f = f->next)
num_fields += 1;
const char *short_name = def->name;
if (strchr(short_name, '$'))
short_name = strrchr(short_name, '$') + 1;
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 (struct_->fields && !struct_->fields->next) { // Single member, can just use its methods
type_t *member_t = struct_->fields->type;
switch (member_t->tag) {
case IntType: case NumType:
typeinfo = CORD_all(typeinfo, ".compare=(void*)", type_to_cord(member_t), "$compare, "
".equal=(void*)", type_to_cord(member_t), "$equal, ");
goto got_methods;
case BigIntType: case TextType:
typeinfo = CORD_all(typeinfo, ".hash=(void*)", type_to_cord(member_t), "$hash", ", ",
".compare=(void*)", type_to_cord(member_t), "$compare, "
".equal=(void*)", type_to_cord(member_t), "$equal, ");
goto got_methods;
case BoolType: goto got_methods;
default: break;
}
}
if (struct_->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);
} else {
// If there are no fields, we can use an EmptyStructInfo typeinfo, which generates less code:
CORD typeinfo = CORD_asprintf("public const TypeInfo_t %r = {%zu, %zu, {.tag=EmptyStructInfo, .EmptyStructInfo.name=%r}};\n",
full_name, type_size(t), type_align(t), CORD_quoted(def->name));
env->code->typeinfos = CORD_all(env->code->typeinfos, typeinfo);
env->code->typeinfos = CORD_all("public const TypeInfo_t ", full_name, ";\n", env->code->typeinfos);
CORD typeinfo = CORD_asprintf("public const TypeInfo_t %r = {%zu, %zu, .tag=StructInfo, .StructInfo.name=\"%s\"%s, "
".StructInfo.num_fields=%ld, .StructInfo.fields=(NamedType_t[%d]){",
full_name, type_size(t), type_align(t), short_name, def->secret ? ", .StructInfo.is_secret=true" : "",
num_fields, num_fields);
for (arg_ast_t *f = def->fields; f; f = f->next) {
type_t *field_type = get_arg_ast_type(env, f);
typeinfo = CORD_all(typeinfo, "{\"", f->name, "\", ", compile_type_info(env, field_type), "}");
if (f->next) typeinfo = CORD_all(typeinfo, ", ");
}
typeinfo = CORD_all(typeinfo, "}};\n");
env->code->typeinfos = CORD_all(env->code->typeinfos, typeinfo);
compile_namespace(env, def->name, def->namespace);
}

View File

@ -431,7 +431,7 @@ PUREFUNC size_t type_size(type_t *t)
type_t *nonnull = Match(t, OptionalType)->type;
switch (nonnull->tag) {
case IntType:
switch (Match(t, IntType)->bits) {
switch (Match(nonnull, IntType)->bits) {
case TYPE_IBITS64: return sizeof(OptionalInt64_t);
case TYPE_IBITS32: return sizeof(OptionalInt32_t);
case TYPE_IBITS16: return sizeof(OptionalInt16_t);
@ -511,7 +511,7 @@ PUREFUNC size_t type_align(type_t *t)
type_t *nonnull = Match(t, OptionalType)->type;
switch (nonnull->tag) {
case IntType:
switch (Match(t, IntType)->bits) {
switch (Match(nonnull, IntType)->bits) {
case TYPE_IBITS64: return __alignof__(OptionalInt64_t);
case TYPE_IBITS32: return __alignof__(OptionalInt32_t);
case TYPE_IBITS16: return __alignof__(OptionalInt16_t);