diff --git a/ast.c b/ast.c index 750245f..a191608 100644 --- a/ast.c +++ b/ast.c @@ -161,6 +161,8 @@ CORD ast_to_xml(ast_t *ast) T(DocTest, "%r%r", optional_tagged("expression", data.expr), xml_escape(data.output)) T(Use, "%r%r", optional_tagged("var", data.var), xml_escape(data.path)) T(InlineCCode, "%r", xml_escape(data.code)) + T(Serialize, "%r", ast_to_xml(data.value)) + T(Deserialize, "%r%r", type_ast_to_xml(data.type), ast_to_xml(data.value)) default: return "???"; #undef T } diff --git a/ast.h b/ast.h index 01ca953..0cd5a03 100644 --- a/ast.h +++ b/ast.h @@ -145,6 +145,7 @@ typedef enum { DocTest, Use, InlineCCode, + Serialize, Deserialize, } ast_e; struct ast_s { @@ -326,6 +327,13 @@ struct ast_s { struct type_s *type; type_ast_t *type_ast; } InlineCCode; + struct { + ast_t *value; + } Serialize; + struct { + ast_t *value; + type_ast_t *type; + } Deserialize; } __data; }; diff --git a/compile.c b/compile.c index a426d09..4e094df 100644 --- a/compile.c +++ b/compile.c @@ -50,7 +50,7 @@ CORD promote_to_optional(type_t *t, CORD code) } else if (t->tag == ByteType) { return CORD_all("((OptionalByte_t){", code, "})"); } else if (t->tag == StructType) { - return CORD_all("({ ", compile_type(Type(OptionalType, .type=t)), " nonnull = {.value=", code, "}; nonnull.is_null = false; nonnull; })"); + return CORD_all("({ ", compile_type(Type(OptionalType, .type=t)), " nonnull = {.value=", code, "}; nonnull.is_none = false; nonnull; })"); } else { return code; } @@ -205,6 +205,7 @@ CORD compile_declaration(type_t *t, CORD name) code = CORD_all(code, compile_type(arg->type)); if (arg->next) code = CORD_cat(code, ", "); } + if (!fn->args) code = CORD_all(code, "void"); return CORD_all(code, ")"); } else if (t->tag != ModuleType) { return CORD_all(compile_type(t), " ", name); @@ -269,6 +270,8 @@ CORD compile_type(type_t *t) code = CORD_all(code, compile_type(arg->type)); if (arg->next) code = CORD_cat(code, ", "); } + if (!fn->args) + code = CORD_all(code, "void"); return CORD_all(code, ")"); } case ClosureType: return "Closure_t"; @@ -425,7 +428,7 @@ CORD check_null(type_t *t, CORD value) else if (t->tag == TextType) return CORD_all("((", value, ").length < 0)"); else if (t->tag == IntType || t->tag == ByteType || t->tag == StructType) - return CORD_all("(", value, ").is_null"); + return CORD_all("(", value, ").is_none"); else if (t->tag == EnumType) return CORD_all("((", value, ").tag == 0)"); else if (t->tag == MomentType) @@ -911,7 +914,7 @@ CORD compile_statement(env_t *env, ast_t *ast) all_args = CORD_all(all_args, "$", arg->name, arg->next ? ", " : CORD_EMPTY); CORD pop_code = CORD_EMPTY; - if (fndef->cache->tag == Int && !cache_size.is_null && cache_size.i > 0) { + if (fndef->cache->tag == Int && !cache_size.is_none && cache_size.i > 0) { pop_code = CORD_all("if (cache.entries.length > ", CORD_asprintf("%ld", cache_size.i), ") Table$remove(&cache, cache.entries.data + cache.entries.stride*RNG$int64(default_rng, I(0), I(cache.entries.length-1)), table_type);\n"); } @@ -1904,7 +1907,7 @@ CORD compile_null(type_t *t) case PointerType: return CORD_all("((", compile_type(t), ")NULL)"); case ClosureType: return "NONE_CLOSURE"; case NumType: return "nan(\"null\")"; - case StructType: return CORD_all("((", compile_type(Type(OptionalType, .type=t)), "){.is_null=yes})"); + case StructType: return CORD_all("((", compile_type(Type(OptionalType, .type=t)), "){.is_none=true})"); case EnumType: { env_t *enum_env = Match(t, EnumType)->env; return CORD_all("((", compile_type(t), "){", namespace_prefix(enum_env, enum_env->namespace), "null})"); @@ -2723,6 +2726,14 @@ CORD compile(env_t *env, ast_t *ast) case MethodCall: { auto call = Match(ast, MethodCall); type_t *self_t = get_type(env, call->self); + + if (streq(call->name, "serialize")) { + if (call->args) + code_err(ast, ":serialize() doesn't take any arguments"); + return CORD_all("generic_serialize((", compile_declaration(self_t, "[1]"), "){", + compile(env, call->self), "}, ", compile_type_info(env, self_t), ")"); + } + int64_t pointer_depth = 0; type_t *self_value_t = self_t; for (; self_value_t->tag == PointerType; self_value_t = Match(self_value_t, PointerType)->pointed) @@ -3191,6 +3202,21 @@ CORD compile(env_t *env, ast_t *ast) code_err(call->fn, "This is not a function, it's a %T", fn_t); } } + case Serialize: { + ast_t *value = Match(ast, Serialize)->value; + type_t *t = get_type(env, value); + return CORD_all("generic_serialize((", compile_declaration(t, "[1]"), "){", compile(env, value), "}, ", compile_type_info(env, t), ")"); + } + case Deserialize: { + ast_t *value = Match(ast, Deserialize)->value; + type_t *value_type = get_type(env, value); + if (!type_eq(value_type, Type(ArrayType, Type(ByteType)))) + code_err(value, "This value should be an array of bytes, not a %T", value_type); + type_t *t = parse_type_ast(env, Match(ast, Deserialize)->type); + return CORD_all("({ ", compile_declaration(t, "deserialized"), ";\n" + "generic_deserialize(", compile(env, value), ", &deserialized, ", compile_type_info(env, t), ");\n" + "deserialized; })"); + } case When: { auto original = Match(ast, When); ast_t *when_var = WrapAST(ast, Var, .name="when"); diff --git a/parse.c b/parse.c index 407710b..152a770 100644 --- a/parse.c +++ b/parse.c @@ -54,6 +54,7 @@ static const char *keywords[] = { "yes", "xor", "while", "when", "use", "unless", "struct", "stop", "skip", "return", "or", "not", "no", "mod1", "mod", "pass", "lang", "inline", "in", "if", "func", "for", "extern", "enum", "else", "do", "defer", "and", "NONE", "_min_", "_max_", + "DESERIALIZE", NULL, }; @@ -139,6 +140,7 @@ static PARSER(parse_use); static PARSER(parse_var); static PARSER(parse_when); static PARSER(parse_while); +static PARSER(parse_deserialize); // // Print a parse error and exit (or use the on_err longjmp) @@ -1547,6 +1549,22 @@ PARSER(parse_none) { return NewAST(ctx->file, start, type->end, None, .type=type); } +PARSER(parse_deserialize) { + const char *start = pos; + if (!match_word(&pos, "DESERIALIZE")) + return NULL; + + spaces(&pos); + ast_t *value = expect(ctx, start, &pos, parse_parens, "I expected an expression here"); + + spaces(&pos); + if (!match(&pos, ":")) + parser_err(ctx, pos, pos, "I expected a ':' and a type here"); + + type_ast_t *type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this deserialization"); + return NewAST(ctx->file, start, pos, Deserialize, .value=value, .type=type); +} + PARSER(parse_var) { const char *start = pos; const char* name = get_id(&pos); @@ -1572,6 +1590,7 @@ PARSER(parse_term_no_suffix) { || (term=parse_parens(ctx, pos)) || (term=parse_table(ctx, pos)) || (term=parse_set(ctx, pos)) + || (term=parse_deserialize(ctx, pos)) || (term=parse_var(ctx, pos)) || (term=parse_array(ctx, pos)) || (term=parse_channel(ctx, pos)) diff --git a/stdlib/arrays.c b/stdlib/arrays.c index ff21920..8dcc48a 100644 --- a/stdlib/arrays.c +++ b/stdlib/arrays.c @@ -6,6 +6,7 @@ #include #include "arrays.h" +#include "integers.h" #include "metamethods.h" #include "optionals.h" #include "rng.h" @@ -701,4 +702,46 @@ public PUREFUNC bool Array$is_none(const void *obj, const TypeInfo_t*) return ((Array_t*)obj)->length < 0; } +public void Array$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) +{ + Array_t arr = *(Array_t*)obj; + int64_t len = arr.length; + Int64$serialize(&len, out, pointers, &Int64$info); + auto item_serialize = type->ArrayInfo.item->metamethods.serialize; + if (item_serialize) { + for (int64_t i = 0; i < len; i++) + item_serialize(arr.data + i*arr.stride, out, pointers, type->ArrayInfo.item); + } else if (arr.stride == type->ArrayInfo.item->size) { + fwrite(arr.data, (size_t)type->ArrayInfo.item->size, (size_t)len, out); + } else { + for (int64_t i = 0; i < len; i++) + fwrite(arr.data + i*arr.stride, (size_t)type->ArrayInfo.item->size, 1, out); + } +} + +public void Array$deserialize(FILE *in, void *obj, Array_t *pointers, const TypeInfo_t *type) +{ + int64_t len = -1; + Int64$deserialize(in, &len, pointers, &Int64$info); + int64_t padded_size = type->ArrayInfo.item->size; + if (type->ArrayInfo.item->align > 0 && padded_size % type->ArrayInfo.item->align > 0) + padded_size += type->ArrayInfo.item->align - (padded_size % type->ArrayInfo.item->align); + Array_t arr = { + .length=len, + .data=GC_MALLOC((size_t)(len*padded_size)), + .stride=padded_size, + }; + auto item_deserialize = type->ArrayInfo.item->metamethods.deserialize; + if (item_deserialize) { + for (int64_t i = 0; i < len; i++) + item_deserialize(in, arr.data + i*arr.stride, pointers, type->ArrayInfo.item); + } else if (arr.stride == type->ArrayInfo.item->size) { + fread(arr.data, (size_t)type->ArrayInfo.item->size, (size_t)len, in); + } else { + for (int64_t i = 0; i < len; i++) + fread(arr.data + i*arr.stride, (size_t)type->ArrayInfo.item->size, 1, in); + } + *(Array_t*)obj = arr; +} + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/arrays.h b/stdlib/arrays.h index 192de80..4f7161b 100644 --- a/stdlib/arrays.h +++ b/stdlib/arrays.h @@ -100,6 +100,8 @@ void Array$heap_pop(Array_t *heap, Closure_t comparison, int64_t padded_item_siz Int_t Array$binary_search(Array_t array, void *target, Closure_t comparison); #define Array$binary_search_value(array, target, comparison) \ ({ __typeof(target) _target = target; Array$binary_search(array, &_target, comparison); }) +void Array$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type); +void Array$deserialize(FILE *in, void *obj, Array_t *pointers, const TypeInfo_t *type); #define Array$metamethods ((metamethods_t){ \ .as_text=Array$as_text, \ @@ -107,6 +109,8 @@ Int_t Array$binary_search(Array_t array, void *target, Closure_t comparison); .equal=Array$equal, \ .hash=Array$hash, \ .is_none=Array$is_none, \ + .serialize=Array$serialize, \ + .deserialize=Array$deserialize, \ }) #define Array$info(item_info) &((TypeInfo_t){.size=sizeof(Array_t), .align=__alignof__(Array_t), \ diff --git a/stdlib/bytes.h b/stdlib/bytes.h index 757f986..9bd4e1c 100644 --- a/stdlib/bytes.h +++ b/stdlib/bytes.h @@ -32,9 +32,9 @@ Text_t Byte$hex(Byte_t byte, bool uppercase, bool prefix); typedef struct { Byte_t value; - bool is_null:1; + bool has_value:1; } OptionalByte_t; -#define NONE_BYTE ((OptionalByte_t){.is_null=true}) +#define NONE_BYTE ((OptionalByte_t){.has_value=false}) // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/c_strings.c b/stdlib/c_strings.c index 99632a5..45f69e1 100644 --- a/stdlib/c_strings.c +++ b/stdlib/c_strings.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "text.h" @@ -50,6 +51,24 @@ PUREFUNC public bool CString$is_none(const void *c_str, const TypeInfo_t*) return *(char**)c_str == NULL; } +static void CString$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t*) +{ + const char *str = *(const char **)obj; + int64_t len = (int64_t)strlen(str); + Int64$serialize(&len, out, pointers, &Int64$info); + fwrite(str, sizeof(char), (size_t)len, out); +} + +static void CString$deserialize(FILE *in, void *out, Array_t *pointers, const TypeInfo_t *) +{ + int64_t len = -1; + Int64$deserialize(in, &len, pointers, &Int64$info); + char *str = GC_MALLOC_ATOMIC((size_t)len+1); + fread(str, sizeof(char), (size_t)len, in); + str[len+1] = '\0'; + *(char**)out = str; +} + public const TypeInfo_t CString$info = { .size=sizeof(char*), .align=__alignof__(char*), @@ -59,6 +78,8 @@ public const TypeInfo_t CString$info = { .equal=CString$equal, .as_text=CString$as_text, .is_none=CString$is_none, + .serialize=CString$serialize, + .deserialize=CString$deserialize, }, }; diff --git a/stdlib/channels.c b/stdlib/channels.c index 8ab4e66..ee7ebde 100644 --- a/stdlib/channels.c +++ b/stdlib/channels.c @@ -137,4 +137,21 @@ public PUREFUNC bool Channel$is_none(const void *obj, const TypeInfo_t*) return *(void**)obj == NULL; } +public void Channel$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) +{ + Channel_t *channel = (Channel_t*)obj; + Array$serialize(&channel->items, out, pointers, Array$info(type->ChannelInfo.item)); + Int64$serialize(&channel->max_size, out, pointers, &Int64$info); +} + +public void Channel$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type) +{ + Channel_t *channel = new(Channel_t); + channel->mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; + channel->cond = (pthread_cond_t)PTHREAD_COND_INITIALIZER; + Array$deserialize(in, &channel->items, pointers, Array$info(type->ChannelInfo.item)); + Int64$deserialize(in, &channel->max_size, pointers, &Int64$info); + *(Channel_t**)outval = channel; +} + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/channels.h b/stdlib/channels.h index 9a84810..cd0a82f 100644 --- a/stdlib/channels.h +++ b/stdlib/channels.h @@ -25,6 +25,8 @@ PUREFUNC int32_t Channel$compare(const void *x, const void *y, const TypeInfo_t PUREFUNC bool Channel$equal(const void *x, const void *y, const TypeInfo_t *type); Text_t Channel$as_text(const void *channel, bool colorize, const TypeInfo_t *type); PUREFUNC bool Channel$is_none(const void *obj, const TypeInfo_t*); +void Channel$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t*); +void Channel$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t*); #define Channel$metamethods ((metamethods_t){ \ .as_text=Channel$as_text, \ @@ -32,6 +34,8 @@ PUREFUNC bool Channel$is_none(const void *obj, const TypeInfo_t*); .equal=Channel$equal, \ .hash=Channel$hash, \ .is_none=Channel$is_none, \ + .serialize=Channel$serialize, \ + .deserialize=Channel$deserialize, \ }) #define Channel$info(item_info) &((TypeInfo_t){.size=sizeof(Channel_t), .align=__alignof__(Channel_t), \ diff --git a/stdlib/enums.c b/stdlib/enums.c index ea2a443..a797c11 100644 --- a/stdlib/enums.c +++ b/stdlib/enums.c @@ -7,6 +7,7 @@ #include "bools.h" #include "channels.h" #include "functiontype.h" +#include "integers.h" #include "metamethods.h" #include "optionals.h" #include "pointers.h" @@ -89,4 +90,32 @@ PUREFUNC public bool Enum$is_none(const void *x, const TypeInfo_t*) return *(int32_t*)x == 0; } +public void Enum$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) +{ + int32_t tag = *(int32_t*)obj; + Int32$serialize(&tag, out, pointers, &Int32$info); + + NamedType_t value = type->EnumInfo.tags[tag-1]; + if (value.type && value.type->size > 0) { + 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); + _serialize(obj + byte_offset, out, pointers, value.type); + } +} + +public void Enum$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type) +{ + int32_t tag = 0; + Int32$deserialize(in, &tag, pointers, &Int32$info); + + NamedType_t value = type->EnumInfo.tags[tag-1]; + if (value.type && value.type->size > 0) { + 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); + _deserialize(in, outval + byte_offset, pointers, value.type); + } +} + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/enums.h b/stdlib/enums.h index 8afa5c9..35c3a22 100644 --- a/stdlib/enums.h +++ b/stdlib/enums.h @@ -12,6 +12,8 @@ PUREFUNC int32_t Enum$compare(const void *x, const void *y, const TypeInfo_t *ty PUREFUNC bool Enum$equal(const void *x, const void *y, const TypeInfo_t *type); PUREFUNC Text_t Enum$as_text(const void *obj, bool colorize, const TypeInfo_t *type); PUREFUNC bool Enum$is_none(const void *obj, const TypeInfo_t *type); +void Enum$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type); +void Enum$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type); #define Enum$metamethods ((metamethods_t){ \ .as_text=Enum$as_text, \ @@ -19,6 +21,8 @@ PUREFUNC bool Enum$is_none(const void *obj, const TypeInfo_t *type); .equal=Enum$equal, \ .hash=Enum$hash, \ .is_none=Enum$is_none, \ + .serialize=Enum$serialize, \ + .deserialize=Enum$deserialize, \ }) // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/functiontype.h b/stdlib/functiontype.h index 7745bff..4dfef3b 100644 --- a/stdlib/functiontype.h +++ b/stdlib/functiontype.h @@ -2,6 +2,7 @@ #include +#include "metamethods.h" #include "types.h" #include "util.h" @@ -15,6 +16,8 @@ PUREFUNC bool Func$is_none(const void *obj, const TypeInfo_t*); #define Func$metamethods ((metamethods_t){ \ .as_text=Func$as_text, \ .is_none=Func$is_none, \ + .serialize=cannot_serialize, \ + .deserialize=cannot_deserialize, \ }) #define Function$info(typestr) &((TypeInfo_t){.size=sizeof(void*), .align=__alignof__(void*), \ diff --git a/stdlib/integers.c b/stdlib/integers.c index 6c00527..b40657b 100644 --- a/stdlib/integers.c +++ b/stdlib/integers.c @@ -1,4 +1,6 @@ // Integer type infos and methods +#include // Must be before gmp.h + #include #include #include @@ -367,6 +369,21 @@ static bool Int$is_none(const void *i, const TypeInfo_t*) return ((Int_t*)i)->small == 0; } +static void Int$serialize(const void *obj, FILE *out, Table_t*, const TypeInfo_t*) +{ + mpz_t n; + mpz_init_set_int(n, *(Int_t*)obj); + mpz_out_raw(out, n); +} + +static void Int$deserialize(FILE *in, void *obj, Array_t*, const TypeInfo_t*) +{ + mpz_t n; + mpz_init(n); + mpz_inp_raw(n, in); + *(Int_t*)obj = Int$from_mpz(n); +} + public const TypeInfo_t Int$info = { .size=sizeof(Int_t), .align=__alignof__(Int_t), @@ -376,9 +393,61 @@ public const TypeInfo_t Int$info = { .hash=Int$hash, .as_text=Int$as_text, .is_none=Int$is_none, + .serialize=Int$serialize, + .deserialize=Int$deserialize, }, }; +public void Int64$serialize(const void *obj, FILE *out, Table_t*, const TypeInfo_t*) +{ + int64_t i = *(int64_t*)obj; + uint64_t z = (uint64_t)((i << 1) ^ (i >> 63)); // Zigzag encode + while (z >= 0x80) { + fputc((uint8_t)(z | 0x80), out); + z >>= 7; + } + fputc((uint8_t)z, out); +} + +public void Int64$deserialize(FILE *in, void *outval, Array_t*, const TypeInfo_t*) +{ + uint64_t z = 0; + for(size_t shift = 0; ; shift += 7) { + uint8_t byte = (uint8_t)fgetc(in); + z |= ((uint64_t)(byte & 0x7F)) << shift; + if ((byte & 0x80) == 0) break; + } + *(int64_t*)outval = (int64_t)((z >> 1) ^ -(z & 1)); // Zigzag decode +} + +public void Int32$serialize(const void *obj, FILE *out, Table_t*, const TypeInfo_t*) +{ + int32_t i = *(int32_t*)obj; + uint32_t z = (uint32_t)((i << 1) ^ (i >> 31)); // Zigzag encode + while (z >= 0x80) { + fputc((uint8_t)(z | 0x80), out); + z >>= 7; + } + fputc((uint8_t)z, out); +} + +public void Int32$deserialize(FILE *in, void *outval, Array_t*, const TypeInfo_t*) +{ + uint32_t z = 0; + for(size_t shift = 0; ; shift += 7) { + uint8_t byte = (uint8_t)fgetc(in); + z |= ((uint32_t)(byte & 0x7F)) << shift; + if ((byte & 0x80) == 0) break; + } + *(int32_t*)outval = (int32_t)((z >> 1) ^ -(z & 1)); // Zigzag decode +} + +// The space savings for smaller ints are not worth having: +#define Int16$serialize NULL +#define Int16$deserialize NULL +#define Int8$serialize NULL +#define Int8$deserialize NULL + #define DEFINE_INT_TYPE(c_type, KindOfInt, fmt, min_val, max_val, to_attr)\ public Text_t KindOfInt ## $as_text(const void *i, bool colorize, const TypeInfo_t*) { \ if (!i) return Text(#KindOfInt); \ @@ -416,12 +485,12 @@ public const TypeInfo_t Int$info = { } \ public PUREFUNC Optional ## KindOfInt ## _t KindOfInt ## $parse(Text_t text) { \ OptionalInt_t full_int = Int$parse(text); \ - if (full_int.small == 0) return (Optional ## KindOfInt ## _t){.is_null=true}; \ + if (full_int.small == 0) return (Optional ## KindOfInt ## _t){.is_none=true}; \ if (Int$compare_value(full_int, I(min_val)) < 0) { \ - return (Optional ## KindOfInt ## _t){.is_null=true}; \ + return (Optional ## KindOfInt ## _t){.is_none=true}; \ } \ if (Int$compare_value(full_int, I(max_val)) > 0) { \ - return (Optional ## KindOfInt ## _t){.is_null=true}; \ + return (Optional ## KindOfInt ## _t){.is_none=true}; \ } \ return (Optional ## KindOfInt ## _t){.i=Int_to_ ## KindOfInt(full_int, true)}; \ } \ @@ -433,6 +502,8 @@ public const TypeInfo_t Int$info = { .metamethods={ \ .compare=KindOfInt##$compare, \ .as_text=KindOfInt##$as_text, \ + .serialize=KindOfInt##$serialize, \ + .deserialize=KindOfInt##$deserialize, \ }, \ }; diff --git a/stdlib/integers.h b/stdlib/integers.h index a5016d4..5556c19 100644 --- a/stdlib/integers.h +++ b/stdlib/integers.h @@ -25,7 +25,7 @@ #define DEFINE_INT_TYPE(c_type, type_name, to_attr) \ typedef struct { \ c_type i; \ - bool is_null:1; \ + bool is_none:1; \ } Optional ## type_name ## _t; \ Text_t type_name ## $as_text(const void *i, bool colorize, const TypeInfo_t *type); \ PUREFUNC int32_t type_name ## $compare(const void *x, const void *y, const TypeInfo_t *type); \ @@ -73,16 +73,21 @@ DEFINE_INT_TYPE(int16_t, Int16, CONSTFUNC) DEFINE_INT_TYPE(int8_t, Int8, CONSTFUNC) #undef DEFINE_INT_TYPE -#define NONE_INT64 ((OptionalInt64_t){.is_null=true}) -#define NONE_INT32 ((OptionalInt32_t){.is_null=true}) -#define NONE_INT16 ((OptionalInt16_t){.is_null=true}) -#define NONE_INT8 ((OptionalInt8_t){.is_null=true}) +#define NONE_INT64 ((OptionalInt64_t){.is_none=true}) +#define NONE_INT32 ((OptionalInt32_t){.is_none=true}) +#define NONE_INT16 ((OptionalInt16_t){.is_none=true}) +#define NONE_INT8 ((OptionalInt8_t){.is_none=true}) #define Int64$abs(...) I64(labs(__VA_ARGS__)) #define Int32$abs(...) I32(abs(__VA_ARGS__)) #define Int16$abs(...) I16(abs(__VA_ARGS__)) #define Int8$abs(...) I8(abs(__VA_ARGS__)) +void Int64$serialize(const void *obj, FILE *out, Table_t*, const TypeInfo_t*); +void Int64$deserialize(FILE *in, void *outval, Array_t*, const TypeInfo_t*); +void Int32$serialize(const void *obj, FILE *out, Table_t*, const TypeInfo_t*); +void Int32$deserialize(FILE *in, void *outval, Array_t*, const TypeInfo_t*); + #define OptionalInt_t Int_t Text_t Int$as_text(const void *i, bool colorize, const TypeInfo_t *type); diff --git a/stdlib/memory.c b/stdlib/memory.c index 70ca574..1805fb6 100644 --- a/stdlib/memory.c +++ b/stdlib/memory.c @@ -8,6 +8,7 @@ #include #include "memory.h" +#include "metamethods.h" #include "text.h" #include "types.h" #include "util.h" @@ -22,6 +23,8 @@ public const TypeInfo_t Memory$info = { .align=0, .metamethods={ .as_text=Memory$as_text, + .serialize=cannot_serialize, + .deserialize=cannot_deserialize, }, }; diff --git a/stdlib/metamethods.c b/stdlib/metamethods.c index 28d9773..2871a96 100644 --- a/stdlib/metamethods.c +++ b/stdlib/metamethods.c @@ -5,14 +5,17 @@ #include "arrays.h" #include "bools.h" +#include "bytes.h" #include "channels.h" #include "functiontype.h" +#include "integers.h" #include "metamethods.h" #include "optionals.h" #include "pointers.h" #include "siphash.h" #include "tables.h" #include "text.h" +#include "types.h" #include "util.h" PUREFUNC public uint64_t generic_hash(const void *obj, const TypeInfo_t *type) @@ -51,10 +54,72 @@ public Text_t generic_as_text(const void *obj, bool colorize, const TypeInfo_t * return type->metamethods.as_text(obj, colorize, type); } +public void _serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) +{ + if (type->metamethods.serialize) + return type->metamethods.serialize(obj, out, pointers, type); + + fwrite(obj, (size_t)type->size, 1, out); +} + +public Array_t generic_serialize(const void *x, const TypeInfo_t *type) +{ + char *buf = NULL; + size_t size = 0; + FILE *stream = open_memstream(&buf, &size); + Table_t pointers = {}; + _serialize(x, stream, &pointers, type); + fclose(stream); + Array_t bytes = { + .data=GC_MALLOC_ATOMIC(size), + .length=(int64_t)size, + .stride=1, + .atomic=1, + }; + memcpy(bytes.data, buf, size); + free(buf); + return bytes; +} + +public void _deserialize(FILE *input, void *outval, Array_t *pointers, const TypeInfo_t *type) +{ + if (type->metamethods.deserialize) { + type->metamethods.deserialize(input, outval, pointers, type); + return; + } + + fread(outval, (size_t)type->size, 1, input); +} + +public void generic_deserialize(Array_t bytes, void *outval, const TypeInfo_t *type) +{ + if (bytes.stride != 1) + Array$compact(&bytes, 1); + + FILE *input = fmemopen(bytes.data, (size_t)bytes.length, "r"); + Array_t pointers = {}; + _deserialize(input, outval, &pointers, type); + fclose(input); +} + public int generic_print(const void *obj, bool colorize, const TypeInfo_t *type) { Text_t text = generic_as_text(obj, colorize, type); return Text$print(stdout, text) + printf("\n"); } +__attribute__((noreturn)) +public void cannot_serialize(const void*, FILE*, Table_t*, const TypeInfo_t *type) +{ + Text_t typestr = generic_as_text(NULL, false, type); + fail("Values of type %k cannot be serialized or deserialized!", &typestr); +} + +__attribute__((noreturn)) +public void cannot_deserialize(FILE*, void*, Array_t*, const TypeInfo_t *type) +{ + Text_t typestr = generic_as_text(NULL, false, type); + fail("Values of type %k cannot be serialized or deserialized!", &typestr); +} + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/metamethods.h b/stdlib/metamethods.h index d77f41c..a75fcf7 100644 --- a/stdlib/metamethods.h +++ b/stdlib/metamethods.h @@ -3,6 +3,7 @@ #include +#include "datatypes.h" #include "types.h" #include "util.h" @@ -10,6 +11,12 @@ PUREFUNC uint64_t generic_hash(const void *obj, const TypeInfo_t *type); PUREFUNC int32_t generic_compare(const void *x, const void *y, const TypeInfo_t *type); PUREFUNC bool generic_equal(const void *x, const void *y, const TypeInfo_t *type); Text_t generic_as_text(const void *obj, bool colorize, const TypeInfo_t *type); +void _serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type); +Array_t generic_serialize(const void *x, const TypeInfo_t *type); +void _deserialize(FILE *input, void *outval, Array_t *pointers, const TypeInfo_t *type); +void generic_deserialize(Array_t bytes, void *outval, const TypeInfo_t *type); int generic_print(const void *obj, bool colorize, const TypeInfo_t *type); +void cannot_serialize(const void*, FILE*, Table_t*, const TypeInfo_t *type); +void cannot_deserialize(FILE*, void*, Array_t*, const TypeInfo_t *type); // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/moments.c b/stdlib/moments.c index ba72ff7..e83bae5 100644 --- a/stdlib/moments.c +++ b/stdlib/moments.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/stdlib/optionals.c b/stdlib/optionals.c index 810f617..38cd470 100644 --- a/stdlib/optionals.c +++ b/stdlib/optionals.c @@ -13,7 +13,7 @@ #include "threads.h" #include "util.h" -public PUREFUNC bool is_null(const void *obj, const TypeInfo_t *non_optional_type) +public PUREFUNC bool is_none(const void *obj, const TypeInfo_t *non_optional_type) { if (non_optional_type->metamethods.is_none) return non_optional_type->metamethods.is_none(obj, non_optional_type); @@ -23,14 +23,14 @@ public PUREFUNC bool is_null(const void *obj, const TypeInfo_t *non_optional_typ PUREFUNC public uint64_t Optional$hash(const void *obj, const TypeInfo_t *type) { - return is_null(obj, type->OptionalInfo.type) ? 0 : generic_hash(obj, type->OptionalInfo.type); + return is_none(obj, type->OptionalInfo.type) ? 0 : generic_hash(obj, type->OptionalInfo.type); } PUREFUNC public int32_t Optional$compare(const void *x, const void *y, const TypeInfo_t *type) { if (x == y) return 0; - bool x_is_null = is_null(x, type->OptionalInfo.type); - bool y_is_null = is_null(y, type->OptionalInfo.type); + bool x_is_null = is_none(x, type->OptionalInfo.type); + bool y_is_null = is_none(y, type->OptionalInfo.type); if (x_is_null && y_is_null) return 0; 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); @@ -40,8 +40,8 @@ PUREFUNC public bool Optional$equal(const void *x, const void *y, const TypeInfo { if (x == y) return true; - bool x_is_null = is_null(x, type->OptionalInfo.type); - bool y_is_null = is_null(y, type->OptionalInfo.type); + bool x_is_null = is_none(x, type->OptionalInfo.type); + bool y_is_null = is_none(y, type->OptionalInfo.type); if (x_is_null && y_is_null) return true; else if (x_is_null != y_is_null) return false; else return generic_equal(x, y, type->OptionalInfo.type); @@ -52,9 +52,42 @@ public Text_t Optional$as_text(const void *obj, bool colorize, const TypeInfo_t if (!obj) return Text$concat(generic_as_text(obj, colorize, type->OptionalInfo.type), Text("?")); - if (is_null(obj, type->OptionalInfo.type)) + if (is_none(obj, type->OptionalInfo.type)) return colorize ? Text("\x1b[31mNONE\x1b[m") : Text("NONE"); return generic_as_text(obj, colorize, type->OptionalInfo.type); } +public void Optional$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) +{ + bool has_value = !is_none(obj, type->OptionalInfo.type); + fputc((int)has_value, out); + if (has_value) + _serialize(obj, out, pointers, type->OptionalInfo.type); +} + +public void Optional$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type) +{ + bool has_value = (bool)fgetc(in); + const TypeInfo_t *nonnull = type->OptionalInfo.type; + if (has_value) { + memset(outval, 0, (size_t)type->size); + _deserialize(in, outval, pointers, nonnull); + } else { + if (nonnull->tag == TextInfo) + *(Text_t*)outval = (Text_t){.length=-1}; + else if (nonnull->tag == ArrayInfo) + *(Array_t*)outval = (Array_t){.length=-1}; + else if (nonnull->tag == TableInfo) + *(Table_t*)outval = (Table_t){.entries={.length=-1}}; + else if (nonnull == &Num$info) + *(double*)outval = NAN; + else if (nonnull == &Num32$info) + *(float*)outval = NAN; + else if (nonnull->tag == StructInfo || (nonnull->tag == OpaqueInfo && type->size > nonnull->size)) + memset(outval + type->size, -1, (size_t)(type->size - nonnull->size)); + else + memset(outval, 0, (size_t)type->size); + } +} + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1 diff --git a/stdlib/optionals.h b/stdlib/optionals.h index 53f2aa9..4e6bf7e 100644 --- a/stdlib/optionals.h +++ b/stdlib/optionals.h @@ -29,12 +29,16 @@ PUREFUNC uint64_t Optional$hash(const void *obj, const TypeInfo_t *type); PUREFUNC int32_t Optional$compare(const void *x, const void *y, const TypeInfo_t *type); PUREFUNC bool Optional$equal(const void *x, const void *y, const TypeInfo_t *type); Text_t Optional$as_text(const void *obj, bool colorize, const TypeInfo_t *type); +void Optional$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type); +void Optional$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type); #define Optional$metamethods ((metamethods_t){ \ .hash=Optional$hash, \ .compare=Optional$compare, \ .equal=Optional$equal, \ .as_text=Optional$as_text, \ + .serialize=Optional$serialize, \ + .deserialize=Optional$deserialize, \ }) #define Optional$info(_size, _align, t) &((TypeInfo_t){.size=_size, .align=_align, \ diff --git a/stdlib/pointers.c b/stdlib/pointers.c index 97c7f5d..4034896 100644 --- a/stdlib/pointers.c +++ b/stdlib/pointers.c @@ -83,4 +83,43 @@ PUREFUNC public bool Pointer$is_none(const void *x, const TypeInfo_t*) return *(void**)x == NULL; } +public void Pointer$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) +{ + void *ptr = *(void**)obj; + assert(ptr != NULL); + + const TypeInfo_t ptr_to_int_table = {.size=sizeof(Table_t), .align=__alignof__(Table_t), + .tag=TableInfo, .TableInfo.key=type, .TableInfo.value=&Int64$info}; + + int64_t *id_ptr = Table$get(*pointers, &ptr, &ptr_to_int_table); + int64_t id; + if (id_ptr) { + id = *id_ptr; + } else { + id = pointers->entries.length + 1; + Table$set(pointers, &ptr, &id, &ptr_to_int_table); + } + + Int64$serialize(&id, out, pointers, &Int64$info); + + if (!id_ptr) + _serialize(ptr, out, pointers, type->PointerInfo.pointed); +} + +public void Pointer$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type) +{ + int64_t id = 0; + Int64$deserialize(in, &id, pointers, &Int64$info); + assert(id != 0); + + if (id > pointers->length) { + void *obj = GC_MALLOC((size_t)type->PointerInfo.pointed->size); + Array$insert(pointers, &obj, I(0), sizeof(void*)); + _deserialize(in, obj, pointers, type->PointerInfo.pointed); + *(void**)outval = obj; + } else { + *(void**)outval = *(void**)(pointers->data + (id-1)*pointers->stride); + } +} + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/pointers.h b/stdlib/pointers.h index 0c9f2ad..1cd7636 100644 --- a/stdlib/pointers.h +++ b/stdlib/pointers.h @@ -5,6 +5,7 @@ #include #include +#include "datatypes.h" #include "types.h" #include "util.h" @@ -12,6 +13,8 @@ Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo_t *type); PUREFUNC int32_t Pointer$compare(const void *x, const void *y, const TypeInfo_t *type); PUREFUNC bool Pointer$equal(const void *x, const void *y, const TypeInfo_t *type); PUREFUNC bool Pointer$is_none(const void *x, const TypeInfo_t*); +void Pointer$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type); +void Pointer$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type); #define Null(t) (t*)NULL #define POINTER_TYPE(_sigil, _pointed) (&(TypeInfo_t){\ @@ -22,6 +25,8 @@ PUREFUNC bool Pointer$is_none(const void *x, const TypeInfo_t*); .compare=Pointer$compare, \ .equal=Pointer$equal, \ .is_none=Pointer$is_none, \ + .serialize=Pointer$serialize, \ + .deserialize=Pointer$deserialize, \ }) #define Pointer$info(sigil_expr, pointed_info) &((TypeInfo_t){.size=sizeof(void*), .align=__alignof__(void*), \ diff --git a/stdlib/stdlib.c b/stdlib/stdlib.c index 6d0ddca..d56db0e 100644 --- a/stdlib/stdlib.c +++ b/stdlib/stdlib.c @@ -76,24 +76,24 @@ static bool parse_single_arg(const TypeInfo_t *info, char *arg, void *dest) return parsed.small != 0; } else if (info == &Int64$info) { OptionalInt64_t parsed = Int64$parse(Text$from_str(arg)); - if (!parsed.is_null) + if (!parsed.is_none) *(OptionalInt64_t*)dest = parsed; - return !parsed.is_null; + return !parsed.is_none; } else if (info == &Int32$info) { OptionalInt32_t parsed = Int32$parse(Text$from_str(arg)); - if (!parsed.is_null) + if (!parsed.is_none) *(OptionalInt32_t*)dest = parsed; - return !parsed.is_null; + return !parsed.is_none; } else if (info == &Int16$info) { OptionalInt16_t parsed = Int16$parse(Text$from_str(arg)); - if (!parsed.is_null) + if (!parsed.is_none) *(OptionalInt16_t*)dest = parsed; - return !parsed.is_null; + return !parsed.is_none; } else if (info == &Int8$info) { OptionalInt8_t parsed = Int8$parse(Text$from_str(arg)); - if (!parsed.is_null) + if (!parsed.is_none) *(OptionalInt8_t*)dest = parsed; - return !parsed.is_null; + return !parsed.is_none; } else if (info == &Bool$info) { OptionalBool_t parsed = Bool$parse(Text$from_str(arg)); if (parsed != NONE_BOOL) diff --git a/stdlib/structs.c b/stdlib/structs.c index c23e4b7..624fe93 100644 --- a/stdlib/structs.c +++ b/stdlib/structs.c @@ -167,4 +167,58 @@ PUREFUNC public bool Struct$is_none(const void *obj, const TypeInfo_t *type) return *(bool*)(obj + type->size); } +public void Struct$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) +{ + 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; + fputc((int)b, out); + 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); + _serialize(obj + byte_offset, out, pointers, field.type); + byte_offset += field.type->size; + } + } +} + +public void Struct$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type) +{ + 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 = (bool)fgetc(in); + *(char*)(outval + byte_offset) |= (b << bit_offset); + 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); + _deserialize(in, outval + byte_offset, pointers, field.type); + byte_offset += field.type->size; + } + } +} + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/structs.h b/stdlib/structs.h index b41dde7..6553cc8 100644 --- a/stdlib/structs.h +++ b/stdlib/structs.h @@ -12,6 +12,8 @@ PUREFUNC int32_t Struct$compare(const void *x, const void *y, const TypeInfo_t * PUREFUNC bool Struct$equal(const void *x, const void *y, const TypeInfo_t *type); PUREFUNC Text_t Struct$as_text(const void *obj, bool colorize, const TypeInfo_t *type); PUREFUNC bool Struct$is_none(const void *obj, const TypeInfo_t *type); +void Struct$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type); +void Struct$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type); #define Struct$metamethods ((metamethods_t){ \ .hash=Struct$hash, \ @@ -19,6 +21,8 @@ PUREFUNC bool Struct$is_none(const void *obj, const TypeInfo_t *type); .equal=Struct$equal, \ .as_text=Struct$as_text, \ .is_none=Struct$is_none, \ + .serialize=Struct$serialize, \ + .deserialize=Struct$deserialize, \ }) // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/tables.c b/stdlib/tables.c index 97fee8a..449f37c 100644 --- a/stdlib/tables.c +++ b/stdlib/tables.c @@ -647,4 +647,41 @@ PUREFUNC public bool Table$is_none(const void *obj, const TypeInfo_t*) return ((Table_t*)obj)->entries.length < 0; } +public void Table$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) +{ + Table_t *t = (Table_t*)obj; + int64_t len = t->entries.length; + Int64$serialize(&len, out, pointers, &Int64$info); + + size_t offset = value_offset(type); + for (int64_t i = 0; i < len; i++) { + _serialize(t->entries.data + i*t->entries.stride, out, pointers, type->TableInfo.key); + _serialize(t->entries.data + i*t->entries.stride + offset, out, pointers, type->TableInfo.value); + } + + Optional$serialize(&t->fallback, out, pointers, Optional$info(sizeof(void*), __alignof__(void*), Pointer$info("&", type))); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstack-protector" +public void Table$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type) +{ + int64_t len; + Int64$deserialize(in, &len, pointers, &Int$info); + + Table_t t = {}; + for (int64_t i = 0; i < len; i++) { + char key[type->TableInfo.key->size]; + _deserialize(in, key, pointers, type->TableInfo.key); + char value[type->TableInfo.value->size]; + _deserialize(in, value, pointers, type->TableInfo.value); + Table$set(&t, key, value, type); + } + + Optional$deserialize(in, &t.fallback, pointers, Optional$info(sizeof(void*), __alignof__(void*), Pointer$info("&", type))); + + *(Table_t*)outval = t; +} +#pragma GCC diagnostic pop + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1 diff --git a/stdlib/tables.h b/stdlib/tables.h index 1f62a13..cd19efd 100644 --- a/stdlib/tables.h +++ b/stdlib/tables.h @@ -77,6 +77,8 @@ PUREFUNC void *Table$str_get_raw(Table_t t, const char *key); void Table$str_set(Table_t *t, const char *key, const void *value); void *Table$str_reserve(Table_t *t, const char *key, const void *value); void Table$str_remove(Table_t *t, const char *key); +void Table$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type); +void Table$deserialize(FILE *in, void *outval, Array_t *pointers, const TypeInfo_t *type); #define Table$length(t) ((t).entries.length) @@ -88,6 +90,8 @@ extern const TypeInfo_t CStrToVoidStarTable; .equal=Table$equal, \ .hash=Table$hash, \ .is_none=Table$is_none, \ + .serialize=Table$serialize, \ + .deserialize=Table$deserialize, \ }) #define Table$info(key_expr, value_expr) &((TypeInfo_t){.size=sizeof(Table_t), .align=__alignof__(Table_t), \ diff --git a/stdlib/text.c b/stdlib/text.c index 40a3988..fed113e 100644 --- a/stdlib/text.c +++ b/stdlib/text.c @@ -1377,6 +1377,24 @@ PUREFUNC public bool Text$is_none(const void *t, const TypeInfo_t*) return ((Text_t*)t)->length < 0; } +public void Text$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *) +{ + const char *str = Text$as_c_string(*(Text_t*)obj); + int64_t len = (int64_t)strlen(str); + Int64$serialize(&len, out, pointers, &Int64$info); + fwrite(str, sizeof(char), (size_t)len, out); +} + +public void Text$deserialize(FILE *in, void *out, Array_t *pointers, const TypeInfo_t *) +{ + int64_t len = -1; + Int64$deserialize(in, &len, pointers, &Int64$info); + char *buf = GC_MALLOC_ATOMIC((size_t)len+1); + fread(buf, sizeof(char), (size_t)len, in); + buf[len+1] = '\0'; + *(Text_t*)out = Text$from_strn(buf, (size_t)len); +} + public const TypeInfo_t Text$info = { .size=sizeof(Text_t), .align=__alignof__(Text_t), diff --git a/stdlib/text.h b/stdlib/text.h index 04c867c..b493f4a 100644 --- a/stdlib/text.h +++ b/stdlib/text.h @@ -60,6 +60,8 @@ Text_t Text$join(Text_t glue, Array_t pieces); Text_t Text$repeat(Text_t text, Int_t count); int32_t Text$get_grapheme_fast(TextIter_t *state, int64_t index); uint32_t Text$get_main_grapheme_fast(TextIter_t *state, int64_t index); +void Text$serialize(const void *obj, FILE *out, Table_t *, const TypeInfo_t *); +void Text$deserialize(FILE *in, void *out, Array_t *, const TypeInfo_t *); MACROLIKE int32_t Text$get_grapheme(Text_t text, int64_t index) { @@ -75,6 +77,8 @@ extern const TypeInfo_t Text$info; .compare=Text$compare, \ .equal=Text$equal, \ .is_none=Text$is_none, \ + .serialize=Text$serialize, \ + .deserialize=Text$deserialize, \ }) // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/threads.c b/stdlib/threads.c index 860b488..c569011 100644 --- a/stdlib/threads.c +++ b/stdlib/threads.c @@ -14,6 +14,7 @@ #include "arrays.h" #include "datatypes.h" +#include "metamethods.h" #include "rng.h" #include "text.h" #include "threads.h" @@ -71,6 +72,8 @@ public const TypeInfo_t Thread$info = { .metamethods={ .as_text=Thread$as_text, .is_none=Thread$is_none, + .serialize=cannot_serialize, + .deserialize=cannot_deserialize, }, }; diff --git a/stdlib/types.h b/stdlib/types.h index 652a2e7..faaa531 100644 --- a/stdlib/types.h +++ b/stdlib/types.h @@ -4,6 +4,7 @@ #include #include +#include #include "datatypes.h" @@ -15,6 +16,8 @@ typedef struct { bool (*equal)(const void*, const void*, const TypeInfo_t*); Text_t (*as_text)(const void*, bool, const TypeInfo_t*); bool (*is_none)(const void*, const TypeInfo_t*); + void (*serialize)(const void*, FILE*, Table_t*, const TypeInfo_t*); + void (*deserialize)(FILE*, void*, Array_t*, const TypeInfo_t*); } metamethods_t; typedef struct { @@ -68,7 +71,8 @@ struct TypeInfo_s { }; #define Type$info(typestr) &((TypeInfo_t){.size=sizeof(TypeInfo_t), .align=__alignof__(TypeInfo_t), \ - .tag=TypeInfoInfo, .TypeInfoInfo.type_str=typestr}) + .tag=TypeInfoInfo, .TypeInfoInfo.type_str=typestr, \ + .metamethods={.serialize=cannot_serialize, .deserialize=cannot_deserialize}}) extern const TypeInfo_t Void$info; extern const TypeInfo_t Abort$info; diff --git a/structs.c b/structs.c index da57ff4..ce5116e 100644 --- a/structs.c +++ b/structs.c @@ -71,7 +71,7 @@ CORD compile_struct_header(env_t *env, ast_t *ast) full_name, "_t value;\n" "struct {\n" "char _padding[", heap_strf("%zu", unpadded_struct_size(t)), "];\n", - "Bool_t is_null;\n" + "Bool_t is_none:1;\n" "};\n" "};\n" "} ", namespace_prefix(env, env->namespace), "$Optional", def->name, "_t;\n" diff --git a/test/serialization.tm b/test/serialization.tm new file mode 100644 index 0000000..218b033 --- /dev/null +++ b/test/serialization.tm @@ -0,0 +1,95 @@ + +struct Foo(name:Text, next=NONE:@Foo) + +enum MyEnum(Zero, One(x:Int), Two(x:Num, y:Text)) + +func main(): + do: + >> obj := now() + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):Moment == obj + = yes + + do: + >> obj := Int64(123) + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):Int64 == obj + = yes + + do: + >> obj := 5 + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):Int == obj + = yes + + do: + >> obj := 9999999999999999999999999999999999999999999999999999 + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):Int == obj + = yes + + do: + >> obj := "Héllo" + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):Text + >> DESERIALIZE(bytes):Text == obj + = yes + + do: + >> obj := [Int64(10), Int64(20), Int64(30)]:reversed() + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):[Int64] == obj + = yes + + do: + >> obj := yes + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):Bool == obj + = yes + + do: + >> obj := @[10, 20] + >> bytes := obj:serialize() + >> roundtrip := DESERIALIZE(bytes):@[Int] + >> roundtrip == obj + = no + >> roundtrip[] == obj[] + = yes + + do: + >> obj := {"A":10, "B":20; fallback={"C":30}} + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):{Text:Int} == obj + = yes + + do: + >> obj := @Foo("root") + >> obj.next = @Foo("abcdef", next=obj) + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):@Foo + = @Foo(name="root", next=@Foo(name="abcdef", next=@~1)) + + do: + >> obj := MyEnum.Two(123, "OKAY") + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):MyEnum == obj + = yes + + do: + >> obj := "Hello"? + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):Text? == obj + = yes + + do: + >> obj := {10, 20, 30} + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):{Int} == obj + = yes + + do: + >> obj := NONE:Num + >> bytes := obj:serialize() + >> DESERIALIZE(bytes):Num? == obj + = yes + diff --git a/typecheck.c b/typecheck.c index a37357d..ba22830 100644 --- a/typecheck.c +++ b/typecheck.c @@ -721,6 +721,11 @@ type_t *get_type(env_t *env, ast_t *ast) } case FunctionCall: { auto call = Match(ast, FunctionCall); + + // HACK: + if (call->fn->tag == Var && streq(Match(call->fn, Var)->name, "serialize")) + return Type(ArrayType, Type(ByteType)); + type_t *fn_type_t = get_type(env, call->fn); if (!fn_type_t) code_err(call->fn, "I couldn't find this function"); @@ -741,6 +746,10 @@ type_t *get_type(env_t *env, ast_t *ast) } case MethodCall: { auto call = Match(ast, MethodCall); + + if (streq(call->name, "serialize")) + return Type(ArrayType, Type(ByteType)); + type_t *self_value_t = value_type(get_type(env, call->self)); switch (self_value_t->tag) { case ArrayType: { @@ -1245,6 +1254,8 @@ type_t *get_type(env_t *env, ast_t *ast) } case Moment: return Type(MomentType); case Unknown: code_err(ast, "I can't figure out the type of: %W", ast); + case Serialize: return Type(ArrayType, Type(ByteType)); + case Deserialize: return parse_type_ast(env, Match(ast, Deserialize)->type); } #pragma GCC diagnostic pop code_err(ast, "I can't figure out the type of: %W", ast);