Initial struct prototype code.

Also add a number of cfunctions for manipulating structs
with prototypes.
This commit is contained in:
Calvin Rose 2021-05-28 21:37:13 -05:00
parent 7c757ef3bf
commit 4d983e54b5
11 changed files with 317 additions and 26 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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");
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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}
};

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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)

View File

@ -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