From 4d983e54b51276f21a6740f36ca6c438dced8c2f Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 28 May 2021 21:37:13 -0500 Subject: [PATCH] Initial struct prototype code. Also add a number of cfunctions for manipulating structs with prototypes. --- src/core/corelib.c | 23 ++++++ src/core/marsh.c | 16 +++- src/core/peg.c | 4 +- src/core/pp.c | 21 +++++- src/core/struct.c | 169 +++++++++++++++++++++++++++++++++++++++++-- src/core/table.c | 69 ++++++++++++++---- src/core/util.h | 1 + src/core/value.c | 13 ++++ src/include/janet.h | 4 + test/suite0010.janet | 18 +++++ tools/format.sh | 5 +- 11 files changed, 317 insertions(+), 26 deletions(-) diff --git a/src/core/corelib.c b/src/core/corelib.c index dc46f91b..f6521f50 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -395,6 +395,23 @@ static Janet janet_core_table(int32_t argc, Janet *argv) { return janet_wrap_table(table); } +static Janet janet_core_getproto(int32_t argc, Janet *argv) { + janet_fixarity(argc, 1); + if (janet_checktype(argv[0], JANET_TABLE)) { + JanetTable *t = janet_unwrap_table(argv[0]); + return t->proto + ? janet_wrap_table(t->proto) + : janet_wrap_nil(); + } + if (janet_checktype(argv[0], JANET_STRUCT)) { + JanetStruct st = janet_unwrap_struct(argv[0]); + return janet_struct_proto(st) + ? janet_wrap_struct(janet_struct_proto(st)) + : janet_wrap_nil(); + } + janet_panicf("expected struct|table, got %v", argv[0]); +} + static Janet janet_core_struct(int32_t argc, Janet *argv) { int32_t i; if (argc & 1) @@ -731,6 +748,11 @@ static const JanetReg corelib_cfuns[] = { JDOC("(signal what x)\n\n" "Raise a signal with payload x. ") }, + { + "getproto", janet_core_getproto, + JDOC("(getproto x)\n\n" + "Get the prototype of a table or struct. Will return nil if `x` has no prototype.") + }, {NULL, NULL, NULL} }; @@ -1013,6 +1035,7 @@ static void janet_load_libs(JanetTable *env) { janet_lib_tuple(env); janet_lib_buffer(env); janet_lib_table(env); + janet_lib_struct(env); janet_lib_fiber(env); janet_lib_os(env); janet_lib_parse(env); diff --git a/src/core/marsh.c b/src/core/marsh.c index 12b83cc6..7fd441c9 100644 --- a/src/core/marsh.c +++ b/src/core/marsh.c @@ -63,7 +63,8 @@ enum { LB_FUNCENV_REF, /* 219 */ LB_FUNCDEF_REF, /* 220 */ LB_UNSAFE_CFUNCTION, /* 221 */ - LB_UNSAFE_POINTER /* 222 */ + LB_UNSAFE_POINTER, /* 222 */ + LB_STRUCT_PROTO, /* 223 */ } LeadBytes; /* Helper to look inside an entry in an environment */ @@ -523,8 +524,10 @@ static void marshal_one(MarshalState *st, Janet x, int flags) { int32_t count; const JanetKV *struct_ = janet_unwrap_struct(x); count = janet_struct_length(struct_); - pushbyte(st, LB_STRUCT); + pushbyte(st, janet_struct_proto(struct_) ? LB_STRUCT_PROTO : LB_STRUCT); pushint(st, count); + if (janet_struct_proto(struct_)) + marshal_one(st, janet_wrap_struct(janet_struct_proto(struct_)), flags + 1); for (int32_t i = 0; i < janet_struct_capacity(struct_); i++) { if (janet_checktype(struct_[i].key, JANET_NIL)) continue; @@ -1257,6 +1260,7 @@ static const uint8_t *unmarshal_one( case LB_ARRAY: case LB_TUPLE: case LB_STRUCT: + case LB_STRUCT_PROTO: case LB_TABLE: case LB_TABLE_PROTO: /* Things that open with integers */ @@ -1286,9 +1290,15 @@ static const uint8_t *unmarshal_one( } *out = janet_wrap_tuple(janet_tuple_end(tup)); janet_v_push(st->lookup, *out); - } else if (lead == LB_STRUCT) { + } else if (lead == LB_STRUCT || lead == LB_STRUCT_PROTO) { /* Struct */ JanetKV *struct_ = janet_struct_begin(len); + if (lead == LB_STRUCT_PROTO) { + Janet proto; + data = unmarshal_one(st, data, &proto, flags + 1); + janet_asserttype(proto, JANET_STRUCT); + janet_struct_proto(struct_) = janet_unwrap_struct(proto); + } for (int32_t i = 0; i < len; i++) { Janet key, value; data = unmarshal_one(st, data, &key, flags + 1); diff --git a/src/core/peg.c b/src/core/peg.c index eb2a2265..6377305f 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -1149,8 +1149,8 @@ static uint32_t peg_compile1(Builder *b, Janet peg) { Janet nextPeg = janet_table_get_ex(grammar, peg, &grammar); if (!grammar || janet_checktype(nextPeg, JANET_NIL)) { nextPeg = (b->default_grammar == NULL) - ? janet_wrap_nil() - : janet_table_get(b->default_grammar, peg); + ? janet_wrap_nil() + : janet_table_get(b->default_grammar, peg); if (janet_checktype(nextPeg, JANET_NIL)) { peg_panic(b, "unknown rule"); } diff --git a/src/core/pp.c b/src/core/pp.c index 3846a660..7ccb0142 100644 --- a/src/core/pp.c +++ b/src/core/pp.c @@ -568,12 +568,12 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { case JANET_STRUCT: case JANET_TABLE: { int istable = janet_checktype(x, JANET_TABLE); - janet_buffer_push_cstring(S->buffer, istable ? "@" : "{"); /* For object-like tables, print class name */ if (istable) { JanetTable *t = janet_unwrap_table(x); JanetTable *proto = t->proto; + janet_buffer_push_cstring(S->buffer, "@"); if (NULL != proto) { Janet name = janet_table_get(proto, janet_ckeywordv("_name")); const uint8_t *n; @@ -588,8 +588,25 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { } } } - janet_buffer_push_cstring(S->buffer, "{"); + } else { + JanetStruct st = janet_unwrap_struct(x); + JanetStruct proto = janet_struct_proto(st); + if (NULL != proto) { + Janet name = janet_struct_get(proto, janet_ckeywordv("_name")); + const uint8_t *n; + int32_t len; + if (janet_bytes_view(name, &n, &len)) { + if (S->flags & JANET_PRETTY_COLOR) { + janet_buffer_push_cstring(S->buffer, janet_class_color); + } + janet_buffer_push_bytes(S->buffer, n, len); + if (S->flags & JANET_PRETTY_COLOR) { + janet_buffer_push_cstring(S->buffer, "\x1B[0m"); + } + } + } } + janet_buffer_push_cstring(S->buffer, "{"); S->depth--; S->indent += 2; diff --git a/src/core/struct.c b/src/core/struct.c index ef34b99e..f8a3bb81 100644 --- a/src/core/struct.c +++ b/src/core/struct.c @@ -39,13 +39,14 @@ JanetKV *janet_struct_begin(int32_t count) { head->length = count; head->capacity = capacity; head->hash = 0; + head->proto = NULL; JanetKV *st = (JanetKV *)(head->data); janet_memempty(st, capacity); return st; } -/* Find an item in a struct. Should be similar to janet_dict_find, but +/* Find an item in a struct without looking for prototypes. Should be similar to janet_dict_find, but * specialized to structs (slightly more compact). */ const JanetKV *janet_struct_find(const JanetKV *st, Janet key) { int32_t cap = janet_struct_capacity(st); @@ -68,7 +69,7 @@ const JanetKV *janet_struct_find(const JanetKV *st, Janet key) { * preforms an in-place insertion sort. This ensures the internal structure of the * hash map is independent of insertion order. */ -void janet_struct_put(JanetKV *st, Janet key, Janet value) { +void janet_struct_put_ext(JanetKV *st, Janet key, Janet value, int replace) { int32_t cap = janet_struct_capacity(st); int32_t hash = janet_hash(key); int32_t index = janet_maphash(cap, hash); @@ -123,13 +124,19 @@ void janet_struct_put(JanetKV *st, Janet key, Janet value) { dist = otherdist; hash = otherhash; } else if (status == 0) { - /* A key was added to the struct more than once - replace old value */ - kv->value = value; + if (replace) { + /* A key was added to the struct more than once - replace old value */ + kv->value = value; + } return; } } } +void janet_struct_put(JanetKV *st, Janet key, Janet value) { + janet_struct_put_ext(st, key, value, 1); +} + /* Finish building a struct */ const JanetKV *janet_struct_end(JanetKV *st) { if (janet_struct_hash(st) != janet_struct_length(st)) { @@ -146,13 +153,52 @@ const JanetKV *janet_struct_end(JanetKV *st) { st = newst; } janet_struct_hash(st) = janet_kv_calchash(st, janet_struct_capacity(st)); + if (janet_struct_proto(st)) { + janet_struct_hash(st) += 2654435761u * janet_struct_hash(janet_struct_proto(st)); + } return (const JanetKV *)st; } +/* Get an item from a struct without looking into prototypes. */ +Janet janet_struct_rawget(const JanetKV *st, Janet key) { + const JanetKV *kv = janet_struct_find(st, key); + return kv ? kv->value : janet_wrap_nil(); +} + /* Get an item from a struct */ Janet janet_struct_get(const JanetKV *st, Janet key) { const JanetKV *kv = janet_struct_find(st, key); - return kv ? kv->value : janet_wrap_nil(); + if (NULL != kv) + return kv->value; + /* Check prototypes */ + { + int i = JANET_MAX_PROTO_DEPTH; + for (st = janet_struct_proto(st); st && i; st = janet_struct_proto(st), --i) { + kv = janet_struct_find(st, key); + if (NULL != kv) + return kv->value; + } + } + return janet_wrap_nil(); +} + +/* Get an item from a struct, and record which prototype the item came from. */ +Janet janet_struct_get_ex(const JanetKV *st, Janet key, JanetStruct *which) { + const JanetKV *kv = janet_struct_find(st, key); + if (NULL != kv) + return kv->value; + /* Check prototypes */ + { + int i = JANET_MAX_PROTO_DEPTH; + for (st = janet_struct_proto(st); st && i; st = janet_struct_proto(st), --i) { + kv = janet_struct_find(st, key); + if (NULL != kv) { + *which = kv; + return kv->value; + } + } + } + return janet_wrap_nil(); } /* Convert struct to table */ @@ -167,3 +213,116 @@ JanetTable *janet_struct_to_table(const JanetKV *st) { } return table; } + +/* C Functions */ + +static Janet cfun_struct_with_proto(int32_t argc, Janet *argv) { + janet_arity(argc, 1, -1); + JanetStruct proto = janet_optstruct(argv, argc, 0, NULL); + if (!(argc & 1)) + janet_panic("expected odd number of arguments"); + JanetKV *st = janet_struct_begin(argc / 2); + janet_struct_proto(st) = proto; + for (int32_t i = 1; i < argc; i += 2) { + janet_struct_put(st, argv[i], argv[i + 1]); + } + return janet_wrap_struct(janet_struct_end(st)); +} + +static Janet cfun_struct_getproto(int32_t argc, Janet *argv) { + janet_fixarity(argc, 1); + JanetStruct st = janet_getstruct(argv, 0); + return janet_struct_proto(st) + ? janet_wrap_struct(janet_struct_proto(st)) + : janet_wrap_nil(); +} + +static Janet cfun_struct_flatten(int32_t argc, Janet *argv) { + janet_fixarity(argc, 1); + JanetStruct st = janet_getstruct(argv, 0); + + /* get an upper bounds on the number of items in the final struct */ + int64_t pair_count = 0; + JanetStruct cursor = st; + while (cursor) { + pair_count += janet_struct_length(cursor); + cursor = janet_struct_proto(cursor); + } + + if (pair_count > INT32_MAX) { + janet_panic("struct too large"); + } + + JanetKV *accum = janet_struct_begin((int32_t) pair_count); + cursor = st; + while (cursor) { + for (int32_t i = 0; i < janet_struct_capacity(cursor); i++) { + const JanetKV *kv = cursor + i; + if (!janet_checktype(kv->key, JANET_NIL)) { + janet_struct_put_ext(accum, kv->key, kv->value, 0); + } + } + cursor = janet_struct_proto(cursor); + } + return janet_wrap_struct(janet_struct_end(accum)); +} + +static Janet cfun_struct_to_table(int32_t argc, Janet *argv) { + janet_arity(argc, 1, 2); + JanetStruct st = janet_getstruct(argv, 0); + int recursive = argc > 1 && janet_truthy(argv[1]); + JanetTable *tab = NULL; + JanetStruct cursor = st; + JanetTable *tab_cursor = tab; + do { + if (tab) { + tab_cursor->proto = janet_table(janet_struct_length(cursor)); + tab_cursor = tab_cursor->proto; + } else { + tab = janet_table(janet_struct_length(cursor)); + tab_cursor = tab; + } + /* TODO - implement as memcpy since struct memory should be compatible + * with table memory */ + for (int32_t i = 0; i < janet_struct_capacity(cursor); i++) { + const JanetKV *kv = cursor + i; + if (!janet_checktype(kv->key, JANET_NIL)) { + janet_table_put(tab_cursor, kv->key, kv->value); + } + } + cursor = janet_struct_proto(cursor); + } while (recursive && cursor); + return janet_wrap_table(tab); +} + +static const JanetReg struct_cfuns[] = { + { + "struct/with-proto", cfun_struct_with_proto, + JDOC("(struct/with-proto proto & kvs)\n\n" + "Create a structure, as with the usual struct constructor but set the " + "struct prototype as well.") + }, + { + "struct/getproto", cfun_struct_getproto, + JDOC("(struct/getproto st)\n\n" + "Get the prototype of a struct, or nil if it doesn't have one.") + }, + { + "struct/proto-flatten", cfun_struct_flatten, + JDOC("(struct/proto-flatten st)\n\n" + "Convert a struct with prototypes to a struct with no prototypes by merging " + "all key value pairs from recursive prototypes into one new struct.") + }, + { + "struct/to-table", cfun_struct_to_table, + JDOC("(struct/to-table st &opt recursive)\n\n" + "Convert a struct to a table. If recursive is true, also convert the " + "table's prototypes into the new struct's prototypes as well.") + }, + {NULL, NULL, NULL} +}; + +/* Load the struct module */ +void janet_lib_struct(JanetTable *env) { + janet_core_cfuns(env, NULL, struct_cfuns); +} diff --git a/src/core/table.c b/src/core/table.c index 616a7c26..e8266a75 100644 --- a/src/core/table.c +++ b/src/core/table.c @@ -208,6 +208,23 @@ void janet_table_put(JanetTable *t, Janet key, Janet value) { } } +/* Used internally so don't check arguments + * Put into a table, but if the key already exists do nothing. */ +static void janet_table_put_no_overwrite(JanetTable *t, Janet key, Janet value) { + JanetKV *bucket = janet_table_find(t, key); + if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) + return; + if (NULL == bucket || 2 * (t->count + t->deleted + 1) > t->capacity) { + janet_table_rehash(t, janet_tablen(2 * t->count + 2)); + } + bucket = janet_table_find(t, key); + if (janet_checktype(bucket->value, JANET_BOOLEAN)) + --t->deleted; + bucket->key = key; + bucket->value = value; + ++t->count; +} + /* Clear a table */ void janet_table_clear(JanetTable *t) { int32_t capacity = t->capacity; @@ -217,19 +234,6 @@ void janet_table_clear(JanetTable *t) { t->deleted = 0; } -/* Convert table to struct */ -const JanetKV *janet_table_to_struct(JanetTable *t) { - JanetKV *st = janet_struct_begin(t->count); - JanetKV *kv = t->data; - JanetKV *end = t->data + t->capacity; - while (kv < end) { - if (!janet_checktype(kv->key, JANET_NIL)) - janet_struct_put(st, kv->key, kv->value); - kv++; - } - return janet_struct_end(st); -} - /* Clone a table. */ JanetTable *janet_table_clone(JanetTable *table) { JanetTable *newTable = janet_gcalloc(JANET_MEMORY_TABLE, sizeof(JanetTable)); @@ -266,6 +270,34 @@ void janet_table_merge_struct(JanetTable *table, const JanetKV *other) { janet_table_mergekv(table, other, janet_struct_capacity(other)); } +/* Convert table to struct */ +const JanetKV *janet_table_to_struct(JanetTable *t) { + JanetKV *st = janet_struct_begin(t->count); + JanetKV *kv = t->data; + JanetKV *end = t->data + t->capacity; + while (kv < end) { + if (!janet_checktype(kv->key, JANET_NIL)) + janet_struct_put(st, kv->key, kv->value); + kv++; + } + return janet_struct_end(st); +} + +JanetTable *janet_table_proto_flatten(JanetTable *t) { + JanetTable *newTable = janet_table(0); + while (t) { + JanetKV *kv = t->data; + JanetKV *end = t->data + t->capacity; + while (kv < end) { + if (!janet_checktype(kv->key, JANET_NIL)) + janet_table_put_no_overwrite(newTable, kv->key, kv->value); + kv++; + } + t = t->proto; + } + return newTable; +} + /* C Functions */ static Janet cfun_table_new(int32_t argc, Janet *argv) { @@ -311,6 +343,12 @@ static Janet cfun_table_clone(int32_t argc, Janet *argv) { return janet_wrap_table(janet_table_clone(table)); } +static Janet cfun_table_proto_flatten(int32_t argc, Janet *argv) { + janet_fixarity(argc, 1); + JanetTable *table = janet_gettable(argv, 0); + return janet_wrap_table(janet_table_proto_flatten(table)); +} + static const JanetReg table_cfuns[] = { { "table/new", cfun_table_new, @@ -350,6 +388,11 @@ static const JanetReg table_cfuns[] = { "Create a copy of a table. Updates to the new table will not change the old table, " "and vice versa.") }, + { + "table/proto-flatten", cfun_table_proto_flatten, + JDOC("(table/proto-flatten tab)\n\n" + "Create a new table that is the result of merging all prototypes into a new table.") + }, {NULL, NULL, NULL} }; diff --git a/src/core/util.h b/src/core/util.h index a2fe173f..a7633da1 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -116,6 +116,7 @@ void janet_lib_array(JanetTable *env); void janet_lib_tuple(JanetTable *env); void janet_lib_buffer(JanetTable *env); void janet_lib_table(JanetTable *env); +void janet_lib_struct(JanetTable *env); void janet_lib_fiber(JanetTable *env); void janet_lib_os(JanetTable *env); void janet_lib_string(JanetTable *env); diff --git a/src/core/value.c b/src/core/value.c index ce43a11c..ea0a95ed 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -104,6 +104,17 @@ static int traversal_next(Janet *x, Janet *y) { janet_vm_traversal = t; return 0; } + /* Traverse prototype */ + JanetStruct sproto = sself->proto; + JanetStruct oproto = sother->proto; + if (sproto && !oproto) return 3; + if (!sproto && oproto) return 1; + if (oproto && sproto) { + *x = janet_wrap_struct(sproto); + *y = janet_wrap_struct(oproto); + janet_vm_traversal = t - 1; + return 0; + } } t--; } @@ -276,6 +287,8 @@ int janet_equals(Janet x, Janet y) { if (s1 == s2) break; if (janet_struct_hash(s1) != janet_struct_hash(s2)) return 0; if (janet_struct_length(s1) != janet_struct_length(s2)) return 0; + if (janet_struct_proto(s1) && !janet_struct_proto(s2)) return 0; + if (!janet_struct_proto(s1) && janet_struct_proto(s2)) return 0; push_traversal_node(janet_struct_head(s1), janet_struct_head(s2), 0); break; } diff --git a/src/include/janet.h b/src/include/janet.h index 0b41987c..bf111e57 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -922,6 +922,7 @@ struct JanetStructHead { int32_t length; int32_t hash; int32_t capacity; + const JanetKV *proto; const JanetKV data[]; }; @@ -1515,10 +1516,13 @@ JANET_API JanetSymbol janet_symbol_gen(void); #define janet_struct_length(t) (janet_struct_head(t)->length) #define janet_struct_capacity(t) (janet_struct_head(t)->capacity) #define janet_struct_hash(t) (janet_struct_head(t)->hash) +#define janet_struct_proto(t) (janet_struct_head(t)->proto) JANET_API JanetKV *janet_struct_begin(int32_t count); JANET_API void janet_struct_put(JanetKV *st, Janet key, Janet value); JANET_API JanetStruct janet_struct_end(JanetKV *st); JANET_API Janet janet_struct_get(JanetStruct st, Janet key); +JANET_API Janet janet_struct_rawget(JanetStruct st, Janet key); +JANET_API Janet janet_struct_get_ex(JanetStruct st, Janet key, JanetStruct *which); JANET_API JanetTable *janet_struct_to_table(JanetStruct st); JANET_API const JanetKV *janet_struct_find(JanetStruct st, Janet key); diff --git a/test/suite0010.janet b/test/suite0010.janet index f42f7acc..3b031906 100644 --- a/test/suite0010.janet +++ b/test/suite0010.janet @@ -161,4 +161,22 @@ ([err] :caught)))) "regression #638")) +# Struct prototypes +(def x (struct/with-proto {1 2 3 4} 5 6)) +(def y (-> x marshal unmarshal)) +(def z {1 2 3 4}) +(assert (= x y) "struct proto marshal equality 1") +(assert (= (getproto x) (getproto y)) "struct proto marshal equality 2") +(assert (= 0 (cmp x y)) "struct proto comparison 1") +(assert (= 0 (cmp (getproto x) (getproto y))) "struct proto comparison 2") +(assert (not= (cmp x z) 0) "struct proto comparison 3") +(assert (not= (cmp y z) 0) "struct proto comparison 4") +(assert (not= x z) "struct proto comparison 5") +(assert (not= y z) "struct proto comparison 6") +(assert (= (x 5) 6) "struct proto get 1") +(assert (= (y 5) 6) "struct proto get 1") +(assert (deep= x y) "struct proto deep= 1") +(assert (deep-not= x z) "struct proto deep= 2") +(assert (deep-not= y z) "struct proto deep= 3") + (end-suite) diff --git a/tools/format.sh b/tools/format.sh index f5b4a88c..37e03f26 100755 --- a/tools/format.sh +++ b/tools/format.sh @@ -3,8 +3,11 @@ # Format all code with astyle STYLEOPTS="--style=attach --indent-switches --convert-tabs \ - --align-pointer=name --pad-header --pad-oper --unpad-paren --indent-labels" + --align-pointer=name --pad-header --pad-oper --unpad-paren --indent-labels --formatted" astyle $STYLEOPTS */*.c astyle $STYLEOPTS */*/*.c astyle $STYLEOPTS */*/*.h +rm -f */*.c.orig +rm -f */*/*.c.orig +rm -f */*/*.h.orig