diff options
Diffstat (limited to 'types.c')
-rw-r--r-- | types.c | 1809 |
1 files changed, 1809 insertions, 0 deletions
@@ -0,0 +1,1809 @@ +/* + * Copyright (C) 2021 Jo-Philipp Wich <jo@mein.io> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdarg.h> +#include <stdlib.h> +#include <assert.h> +#include <endian.h> +#include <errno.h> +#include <math.h> + +#include "types.h" +#include "util.h" +#include "vm.h" + +uc_type_t +ucv_type(uc_value_t *uv) +{ + uc_type_t type = ((uintptr_t)uv & 3); + + if (type == UC_NULL && uv != NULL) + type = uv->type; + + return type; +} + +const char * +ucv_typename(uc_value_t *uv) +{ + switch (ucv_type(uv)) { + case UC_NULL: return "null"; + case UC_INTEGER: return "integer"; + case UC_BOOLEAN: return "boolean"; + case UC_STRING: return "string"; + case UC_DOUBLE: return "double"; + case UC_ARRAY: return "array"; + case UC_OBJECT: return "object"; + case UC_REGEXP: return "regexp"; + case UC_FUNCTION: return "function"; + case UC_CFUNCTION: return "cfunction"; + case UC_CLOSURE: return "closure"; + case UC_UPVALUE: return "upvalue"; + case UC_RESSOURCE: return "ressource"; + } + + return "unknown"; +} + +static uc_ressource_type_t * +ucv_ressource_type_get(size_t type); + +static void +ucv_unref(uc_weakref_t *ref) +{ + ref->prev->next = ref->next; + ref->next->prev = ref->prev; +} + +static void +ucv_ref(uc_weakref_t *head, uc_weakref_t *item) +{ + item->next = head->next; + item->prev = head; + head->next->prev = item; + head->next = item; +} + +#if 0 +static uc_weakref_t * +ucv_get_weakref(uc_value_t *uv) +{ + switch (ucv_type(uv)) { + case UC_ARRAY: + return &((uc_array_t *)uv)->ref; + + case UC_OBJECT: + return &((uc_object_t *)uv)->ref; + + case UC_CLOSURE: + return &((uc_closure_t *)uv)->ref; + + default: + return NULL; + } +} +#endif + +static void +ucv_put_value(uc_value_t *uv, bool retain) +{ + if (uv == NULL || (uintptr_t)uv & 3) + return; + + assert(uv->refcount > 0); + + if (uv->refcount > 0) + uv->refcount--; + + if (uv->refcount == 0) + ucv_free(uv, retain); +} + +static void +ucv_gc_mark(uc_value_t *uv); + +static void +ucv_gc_mark(uc_value_t *uv) +{ + uc_function_t *function; + uc_closure_t *closure; + uc_upvalref_t *upval; + uc_object_t *object; + uc_array_t *array; + struct lh_entry *entry; + size_t i; + + switch (ucv_type(uv)) { + case UC_ARRAY: + array = (uc_array_t *)uv; + + ucv_gc_mark(array->proto); + + for (i = 0; i < array->count; i++) + ucv_gc_mark(array->entries[i]); + + if (array->ref.next) + ucv_set_mark(uv); + + break; + + case UC_OBJECT: + object = (uc_object_t *)uv; + + ucv_gc_mark(object->proto); + + lh_foreach(object->table, entry) + ucv_gc_mark((uc_value_t *)lh_entry_v(entry)); + + if (object->ref.next) + ucv_set_mark(uv); + + break; + + case UC_CLOSURE: + closure = (uc_closure_t *)uv; + function = closure->function; + + for (i = 0; i < function->nupvals; i++) + ucv_gc_mark((uc_value_t *)closure->upvals[i]); + + ucv_gc_mark((uc_value_t *)function); + + if (closure->ref.next) + ucv_set_mark(uv); + + break; + + case UC_UPVALUE: + upval = (uc_upvalref_t *)uv; + ucv_gc_mark(upval->value); + break; + + default: + break; + } +} + +void +ucv_free(uc_value_t *uv, bool retain) +{ + uc_ressource_type_t *restype; + uc_ressource_t *ressource; + uc_function_t *function; + uc_closure_t *closure; + uc_upvalref_t *upval; + uc_regexp_t *regexp; + uc_object_t *object; + uc_array_t *array; + uc_weakref_t *ref; + size_t i; + + if (uv == NULL || (uintptr_t)uv & 3) + return; + + if (uv->mark) + return; + + uv->mark = true; + + ref = NULL; + + switch (uv->type) { + case UC_ARRAY: + array = (uc_array_t *)uv; + ref = &array->ref; + ucv_put_value(array->proto, retain); + + for (i = 0; i < array->count; i++) + ucv_put_value(array->entries[i], retain); + + uc_vector_clear(array); + break; + + case UC_OBJECT: + object = (uc_object_t *)uv; + ref = &object->ref; + ucv_put_value(object->proto, retain); + lh_table_free(object->table); + break; + + case UC_REGEXP: + regexp = (uc_regexp_t *)uv; + regfree(®exp->regexp); + break; + + case UC_FUNCTION: + function = (uc_function_t *)uv; + uc_chunk_free(&function->chunk); + uc_source_put(function->source); + break; + + case UC_CLOSURE: + closure = (uc_closure_t *)uv; + function = closure->function; + ref = &closure->ref; + + for (i = 0; i < function->nupvals; i++) + ucv_put_value((uc_value_t *)closure->upvals[i], retain); + + ucv_put_value((uc_value_t *)function, retain); + break; + + case UC_RESSOURCE: + ressource = (uc_ressource_t *)uv; + restype = ucv_ressource_type_get(ressource->type); + + if (restype && restype->free) + restype->free(ressource->data); + + break; + + case UC_UPVALUE: + upval = (uc_upvalref_t *)uv; + ucv_put_value(upval->value, retain); + break; + } + + if (!ref || !retain) { + if (ref && ref->prev && ref->next) + ucv_unref(ref); + + free(uv); + } + else { + uv->type = UC_NULL; + } +} + +void +ucv_put(uc_value_t *uv) +{ + ucv_put_value(uv, false); +} + +uc_value_t * +ucv_get(uc_value_t *uv) +{ + if (uv == NULL || (uintptr_t)uv & 3) + return uv; + + assert(uv->refcount < 0x03ffffff); + + uv->refcount++; + + return uv; +} + +uc_value_t * +ucv_boolean_new(bool val) +{ + uintptr_t pv = UC_BOOLEAN | (val << 2); + + return (uc_value_t *)pv; +} + +bool +ucv_boolean_get(uc_value_t *uv) +{ + uintptr_t pv = (uintptr_t)uv; + + if ((pv & 3) == UC_BOOLEAN) + return (pv >> 2) & 1; + + return false; +} + + +uc_value_t * +ucv_string_new(const char *str) +{ + return ucv_string_new_length(str, strlen(str)); +} + +uc_value_t * +ucv_string_new_length(const char *str, size_t length) +{ + uc_string_t *ustr; + uintptr_t pv; + size_t i; + char *s; + + if ((length + 1) < sizeof(void *)) { + pv = UC_STRING | (length << 2); + +#if __BYTE_ORDER == __LITTLE_ENDIAN + s = (char *)&pv + 1; +#else + s = (char *)&pv; +#endif + + for (i = 0; i < length; i++) + s[i] = str[i]; + + return (uc_value_t *)pv; + } + + ustr = xalloc(sizeof(*ustr) + length + 1); + ustr->header.type = UC_STRING; + ustr->header.refcount = 1; + ustr->length = length; + memcpy(ustr->str, str, length); + + return &ustr->header; +} + +uc_stringbuf_t * +ucv_stringbuf_new(void) +{ + uc_stringbuf_t *sb = xprintbuf_new(); + uc_string_t ustr = { + .header = { + .type = UC_STRING, + .refcount = 1 + } + }; + + printbuf_memappend_fast(sb, (char *)&ustr, (int)sizeof(ustr)); + + return sb; +} + +uc_value_t * +ucv_stringbuf_finish(uc_stringbuf_t *sb) +{ + uc_string_t *ustr = (uc_string_t *)sb->buf; + + ustr->length = printbuf_length(sb) - offsetof(uc_string_t, str); + + free(sb); + + return &ustr->header; +} + +char * +_ucv_string_get(uc_value_t **uv) +{ + uc_string_t *str; + + switch ((uintptr_t)*uv & 3) { + case UC_STRING: +#if __BYTE_ORDER == __LITTLE_ENDIAN + return (char *)uv + 1; +#else + return (char *)uv; +#endif + + case UC_NULL: + if (*uv != NULL && (*uv)->type == UC_STRING) { + str = (uc_string_t *)*uv; + + return str->str; + } + } + + return NULL; +} + +size_t +ucv_string_length(uc_value_t *uv) +{ + uc_string_t *str = (uc_string_t *)uv; + uintptr_t pv = (uintptr_t)uv; + + if ((pv & 3) == UC_STRING) + return (pv & 0xff) >> 2; + else if (uv != NULL && uv->type == UC_STRING) + return str->length; + + return 0; +} + + +uc_value_t * +ucv_int64_new(int64_t n) +{ + uint64_t uval = (n < 0) ? ((n > INT64_MIN) ? (~n + 1) : INT64_MAX) : n; + uint64_t max = (1ULL << ((sizeof(void *) * 8) - 3)) - 1; + uc_integer_t *integer; + uintptr_t pv; + + if (uval <= max) { + pv = UC_INTEGER | ((n < 0) << 2) | (uval << 3); + + return (uc_value_t *)pv; + } + + integer = xalloc(sizeof(*integer)); + integer->header.type = UC_INTEGER; + integer->header.refcount = 1; + integer->header.u64 = 0; + integer->i.s64 = n; + + return &integer->header; +} + +uc_value_t * +ucv_uint64_new(uint64_t n) +{ + uint64_t max = (1ULL << ((sizeof(void *) * 8) - 3)) - 1; + uc_integer_t *integer; + uintptr_t pv; + + if (n <= max) { + pv = UC_INTEGER | (n << 3); + + return (uc_value_t *)pv; + } + + integer = xalloc(sizeof(*integer)); + integer->header.type = UC_INTEGER; + integer->header.refcount = 1; + integer->header.u64 = 1; + integer->i.u64 = n; + + return &integer->header; +} + +uint64_t +ucv_uint64_get(uc_value_t *uv) +{ + uintptr_t pv = (uintptr_t)uv; + uc_integer_t *integer; + + errno = 0; + + if ((pv & 3) == UC_INTEGER) { + if (((pv >> 2) & 1) == 0) + return (uint64_t)(pv >> 3); + + errno = ERANGE; + + return 0; + } + else if (uv != NULL && uv->type == UC_INTEGER) { + integer = (uc_integer_t *)uv; + + if (integer->header.u64) + return integer->i.u64; + + if (integer->i.s64 >= 0) + return (uint64_t)integer->i.s64; + + errno = ERANGE; + + return 0; + } + + errno = EINVAL; + + return 0; +} + +int64_t +ucv_int64_get(uc_value_t *uv) +{ + uintptr_t pv = (uintptr_t)uv; + uc_integer_t *integer; + + errno = 0; + + if ((pv & 3) == UC_INTEGER) { + if (((pv >> 2) & 1) == 0) + return (int64_t)(pv >> 3); + + return -(int64_t)(pv >> 3); + } + else if (uv != NULL && uv->type == UC_INTEGER) { + integer = (uc_integer_t *)uv; + + if (integer->header.u64 && integer->i.u64 <= INT64_MAX) + return (int64_t)integer->i.u64; + + if (!integer->header.u64) + return integer->i.s64; + + errno = ERANGE; + + return 0; + } + + errno = EINVAL; + + return 0; +} + + +uc_value_t * +ucv_double_new(double d) +{ + uc_double_t *dbl; + + dbl = xalloc(sizeof(*dbl)); + dbl->header.type = UC_DOUBLE; + dbl->header.refcount = 1; + dbl->dbl = d; + + return &dbl->header; +} + +double +ucv_double_get(uc_value_t *uv) +{ + uc_double_t *dbl; + + errno = 0; + + if (ucv_type(uv) != UC_DOUBLE) { + errno = EINVAL; + + return NAN; + } + + dbl = (uc_double_t *)uv; + + return dbl->dbl; +} + + +uc_value_t * +ucv_array_new(uc_vm *vm) +{ + return ucv_array_new_length(vm, 0); +} + +uc_value_t * +ucv_array_new_length(uc_vm *vm, size_t length) +{ + uc_array_t *array; + + /* XXX */ + length = 0; + + array = xalloc(sizeof(*array) + length * sizeof(array->entries[0])); + array->header.type = UC_ARRAY; + array->header.refcount = 1; + + if (length > 0) + array->count = length; + + uc_vector_grow(array); + + if (vm) + ucv_ref(&vm->values, &array->ref); + + return &array->header; +} + +uc_value_t * +ucv_array_pop(uc_value_t *uv) +{ + uc_array_t *array = (uc_array_t *)uv; + uc_value_t *item; + + if (ucv_type(uv) != UC_ARRAY || array->count == 0) + return NULL; + + item = ucv_get(array->entries[array->count - 1]); + + ucv_array_delete(uv, array->count - 1, 1); + + return item; +} + +uc_value_t * +ucv_array_push(uc_value_t *uv, uc_value_t *item) +{ + uc_array_t *array = (uc_array_t *)uv; + + if (ucv_type(uv) != UC_ARRAY) + return NULL; + + ucv_array_set(uv, array->count, item); + + return item; +} + +uc_value_t * +ucv_array_shift(uc_value_t *uv) +{ + uc_array_t *array = (uc_array_t *)uv; + uc_value_t *item; + + if (ucv_type(uv) != UC_ARRAY || array->count == 0) + return NULL; + + item = ucv_get(array->entries[0]); + + ucv_array_delete(uv, 0, 1); + + return item; +} + +uc_value_t * +ucv_array_unshift(uc_value_t *uv, uc_value_t *item) +{ + uc_array_t *array = (uc_array_t *)uv; + size_t i; + + if (ucv_type(uv) != UC_ARRAY || array->count == 0) + return NULL; + + array->count++; + uc_vector_grow(array); + + for (i = array->count; i > 1; i--) + array->entries[i - 1] = array->entries[i - 2]; + + array->entries[0] = item; + + return item; +} + +void +ucv_array_sort(uc_value_t *uv, int (*cmp)(const void *, const void *)) +{ + uc_array_t *array = (uc_array_t *)uv; + + if (ucv_type(uv) != UC_ARRAY || array->count <= 1) + return; + + qsort(array->entries, array->count, sizeof(array->entries[0]), cmp); +} + +bool +ucv_array_delete(uc_value_t *uv, size_t offset, size_t count) +{ + uc_array_t *array = (uc_array_t *)uv; + size_t i; + + if (ucv_type(uv) != UC_ARRAY || array->count == 0) + return false; + + if (offset >= array->count) + return false; + + if ((offset + count) < offset) + return false; + + if ((offset + count) > array->count) + count = array->count - offset; + + for (i = 0; i < count; i++) + ucv_put(array->entries[offset + i]); + + memmove(&array->entries[offset], + &array->entries[offset + count], + (array->count - (offset + count)) * sizeof(array->entries[0])); + + array->count -= count; + + uc_vector_grow(array); + + return true; +} + +bool +ucv_array_set(uc_value_t *uv, size_t index, uc_value_t *item) +{ + uc_array_t *array = (uc_array_t *)uv; + + if (ucv_type(uv) != UC_ARRAY) + return false; + + if (index >= array->count) { + array->count = index + 1; + uc_vector_grow(array); + } + else { + ucv_put(array->entries[index]); + } + + array->entries[index] = item; + + return true; +} + +uc_value_t * +ucv_array_get(uc_value_t *uv, size_t index) +{ + uc_array_t *array = (uc_array_t *)uv; + + if (ucv_type(uv) != UC_ARRAY) + return NULL; + + if (index >= array->count) + return NULL; + + return array->entries[index]; +} +size_t +ucv_array_length(uc_value_t *uv) +{ + uc_array_t *array = (uc_array_t *)uv; + + if (ucv_type(uv) != UC_ARRAY) + return 0; + + return array->count; +} + + +static void +ucv_free_object_entry(struct lh_entry *entry) +{ + free(lh_entry_k(entry)); + ucv_put(lh_entry_v(entry)); +} + +uc_value_t * +ucv_object_new(uc_vm *vm) +{ + struct lh_table *table; + uc_object_t *object; + + table = lh_kchar_table_new(16, ucv_free_object_entry); + + if (!table) { + fprintf(stderr, "Out of memory\n"); + abort(); + } + + object = xalloc(sizeof(*object)); + object->header.type = UC_OBJECT; + object->header.refcount = 1; + object->table = table; + + if (vm) + ucv_ref(&vm->values, &object->ref); + + return &object->header; +} + +bool +ucv_object_add(uc_value_t *uv, const char *key, uc_value_t *val) +{ + uc_object_t *object = (uc_object_t *)uv; + struct lh_entry *existing_entry; + uc_value_t *existing_value; + unsigned long hash; + void *k; + + if (ucv_type(uv) != UC_OBJECT) + return false; + + hash = lh_get_hash(object->table, (const void *)key); + existing_entry = lh_table_lookup_entry_w_hash(object->table, (const void *)key, hash); + + if (existing_entry == NULL) { + k = xstrdup(key); + + if (lh_table_insert_w_hash(object->table, k, val, hash, 0) != 0) { + free(k); + + return false; + } + + return true; + } + + existing_value = (uc_value_t *)lh_entry_v(existing_entry); + + if (existing_value) + ucv_put(existing_value); + + existing_entry->v = val; + + return true; +} + +bool +ucv_object_delete(uc_value_t *uv, const char *key) +{ + uc_object_t *object = (uc_object_t *)uv; + + if (ucv_type(uv) != UC_OBJECT) + return false; + + return (lh_table_delete(object->table, key) == 0); +} + +uc_value_t * +ucv_object_get(uc_value_t *uv, const char *key, bool *found) +{ + uc_object_t *object = (uc_object_t *)uv; + uc_value_t *val = NULL; + bool rv; + + if (found != NULL) + *found = false; + + if (ucv_type(uv) != UC_OBJECT) + return NULL; + + rv = lh_table_lookup_ex(object->table, (const void *)key, (void **)&val); + + if (found != NULL) + *found = rv; + + return val; +} + +size_t +ucv_object_length(uc_value_t *uv) +{ + uc_object_t *object = (uc_object_t *)uv; + + if (ucv_type(uv) != UC_OBJECT) + return 0; + + return lh_table_length(object->table); +} + + +uc_value_t * +ucv_function_new(const char *name, size_t srcpos, uc_source *source) +{ + size_t namelen = 0; + uc_function_t *fn; + + if (name) + namelen = strlen(name); + + fn = xalloc(sizeof(*fn) + namelen + 1); + fn->header.type = UC_FUNCTION; + fn->header.refcount = 1; + + if (name) + strcpy(fn->name, name); + + fn->nargs = 0; + fn->nupvals = 0; + fn->srcpos = srcpos; + fn->source = uc_source_get(source); + fn->vararg = false; + + uc_chunk_init(&fn->chunk); + + return &fn->header; +} + +size_t +ucv_function_srcpos(uc_value_t *uv, size_t off) +{ + uc_function_t *fn = (uc_function_t *)uv; + size_t pos; + + if (ucv_type(uv) != UC_FUNCTION) + return 0; + + pos = uc_chunk_debug_get_srcpos(&fn->chunk, off); + + return pos ? fn->srcpos + pos : 0; +} + + +uc_value_t * +ucv_cfunction_new(const char *name, uc_cfn_ptr_t fptr) +{ + uc_cfunction_t *cfn; + size_t namelen = 0; + + if (name) + namelen = strlen(name); + + cfn = xalloc(sizeof(*cfn) + namelen + 1); + cfn->header.type = UC_CFUNCTION; + cfn->header.refcount = 1; + + if (name) + strcpy(cfn->name, name); + + cfn->cfn = fptr; + + return &cfn->header; +} + + +uc_value_t * +ucv_closure_new(uc_vm *vm, uc_function_t *function, bool arrow_fn) +{ + uc_closure_t *closure; + + closure = xalloc(sizeof(*closure) + (sizeof(uc_upvalref_t *) * function->nupvals)); + closure->header.type = UC_CLOSURE; + closure->header.refcount = 1; + closure->function = function; + closure->is_arrow = arrow_fn; + closure->upvals = function->nupvals ? (uc_upvalref_t **)((uintptr_t)closure + ALIGN(sizeof(*closure))) : NULL; + + if (vm) + ucv_ref(&vm->values, &closure->ref); + + return &closure->header; +} + + +static uc_ressource_types_t res_types; + +uc_ressource_type_t * +ucv_ressource_type_add(const char *name, uc_value_t *proto, void (*freefn)(void *)) +{ + uc_ressource_type_t *existing; + + existing = ucv_ressource_type_lookup(name); + + if (existing) { + ucv_put(proto); + + return existing; + } + + uc_vector_grow(&res_types); + + res_types.entries[res_types.count].name = name; + res_types.entries[res_types.count].proto = proto; + res_types.entries[res_types.count].free = freefn; + + return &res_types.entries[res_types.count++]; +} + +static uc_ressource_type_t * +ucv_ressource_type_get(size_t type) +{ + return (type > 0 && type <= res_types.count) ? &res_types.entries[type - 1] : NULL; +} + +uc_ressource_type_t * +ucv_ressource_type_lookup(const char *name) +{ + size_t i; + + for (i = 0; i < res_types.count; i++) + if (!strcmp(res_types.entries[i].name, name)) + return &res_types.entries[i]; + + return NULL; +} + + +uc_value_t * +ucv_ressource_new(uc_ressource_type_t *type, void *data) +{ + uc_ressource_t *res; + + res = xalloc(sizeof(*res)); + res->header.type = UC_RESSOURCE; + res->header.refcount = 1; + res->type = type ? (type - res_types.entries) + 1 : 0; + res->data = data; + + return &res->header; +} + +void ** +ucv_ressource_dataptr(uc_value_t *uv, const char *name) +{ + uc_ressource_t *res = (uc_ressource_t *)uv; + uc_ressource_type_t *type; + + if (ucv_type(uv) != UC_RESSOURCE) + return NULL; + + if (name) { + type = ucv_ressource_type_lookup(name); + + if (!type || type != ucv_ressource_type_get(res->type)) + return NULL; + } + + return &res->data; +} + + +uc_value_t * +ucv_regexp_new(const char *pattern, bool icase, bool newline, bool global, char **error) +{ + int cflags = REG_EXTENDED, res; + uc_regexp_t *re; + size_t len; + + re = xalloc(sizeof(*re) + strlen(pattern) + 1); + re->header.type = UC_REGEXP; + re->header.refcount = 1; + re->icase = icase; + re->global = global; + re->newline = newline; + strcpy(re->source, pattern); + + if (icase) + cflags |= REG_ICASE; + + if (newline) + cflags |= REG_NEWLINE; + + res = regcomp(&re->regexp, pattern, cflags); + + if (res != 0) { + if (error) { + len = regerror(res, &re->regexp, NULL, 0); + *error = xalloc(len); + + regerror(res, &re->regexp, *error, len); + } + + free(re); + + return NULL; + } + + /* + json_object_object_add(re->header.jso, "source", xjs_new_string(pattern)); + json_object_object_add(re->header.jso, "i", xjs_new_boolean(icase)); + json_object_object_add(re->header.jso, "g", xjs_new_boolean(global)); + json_object_object_add(re->header.jso, "s", xjs_new_boolean(newline)); + */ + + return &re->header; +} + + +uc_value_t * +ucv_upvalref_new(size_t slot) +{ + uc_upvalref_t *up; + + up = xalloc(sizeof(*up)); + up->header.type = UC_UPVALUE; + up->header.refcount = 1; + up->slot = slot; + + return &up->header; +} + + +uc_value_t * +ucv_prototype_get(uc_value_t *uv) +{ + uc_ressource_type_t *restype; + uc_ressource_t *ressource; + uc_object_t *object; + uc_array_t *array; + + switch (ucv_type(uv)) { + case UC_ARRAY: + array = (uc_array_t *)uv; + + return array->proto; + + case UC_OBJECT: + object = (uc_object_t *)uv; + + return object->proto; + + case UC_RESSOURCE: + ressource = (uc_ressource_t *)uv; + restype = ucv_ressource_type_get(ressource->type); + + return restype ? restype->proto : NULL; + + default: + return NULL; + } +} + +bool +ucv_prototype_set(uc_value_t *uv, uc_value_t *proto) +{ + uc_object_t *object; + uc_array_t *array; + + if (ucv_type(proto) != UC_OBJECT) + return false; + + switch (ucv_type(uv)) { + case UC_ARRAY: + array = (uc_array_t *)uv; + array->proto = proto; + + return true; + + case UC_OBJECT: + object = (uc_object_t *)uv; + object->proto = proto; + + return true; + + default: + return false; + } +} + +uc_value_t * +ucv_property_get(uc_value_t *uv, const char *key) +{ + uc_value_t *val; + bool found; + + for (; uv; uv = ucv_prototype_get(uv)) { + val = ucv_object_get(uv, key, &found); + + if (found) + return val; + } + + return NULL; +} + + +uc_value_t * +ucv_from_json(uc_vm *vm, json_object *jso) +{ + //uc_array_t *arr; + uc_value_t *uv, *item; + int64_t n; + size_t i; + + switch (json_object_get_type(jso)) { + case json_type_null: + return NULL; + + case json_type_boolean: + return ucv_boolean_new(json_object_get_boolean(jso)); + + case json_type_double: + return ucv_double_new(json_object_get_double(jso)); + + case json_type_int: + n = json_object_get_int64(jso); + + if (n == INT64_MAX) + return ucv_uint64_new(json_object_get_uint64(jso)); + + return ucv_int64_new(n); + + case json_type_object: + uv = ucv_object_new(vm); + + json_object_object_foreach(jso, key, val) { + item = ucv_from_json(vm, val); + + if (!ucv_object_add(uv, key, item)) + ucv_put(item); + +#ifdef __clang_analyzer__ + /* Clang static analyzer does not understand that the object retains + * our item so pretend to free it here to suppress the false positive + * memory leak warning. */ + ucv_put(item); +#endif + } + + return uv; + + case json_type_array: + /* XXX + arr = (uc_array_t *)ucv_array_new_length(vm, json_object_array_length(jso)); + + for (i = 0; i < arr->count; i++) + arr->entries[i] = ucv_from_json(vm, json_object_array_get_idx(jso, i)); + + return &arr->header; + */ + uv = ucv_array_new(vm); + + for (i = 0; i < json_object_array_length(jso); i++) { + item = ucv_from_json(vm, json_object_array_get_idx(jso, i)); + + if (!ucv_array_push(uv, item)) + ucv_put(item); + +#ifdef __clang_analyzer__ + /* Clang static analyzer does not understand that the array retains + * our item so pretend to free it here to suppress the false positive + * memory leak warning. */ + ucv_put(item); +#endif + } + + return uv; + + case json_type_string: + return ucv_string_new_length(json_object_get_string(jso), json_object_get_string_len(jso)); + } + + return NULL; +} + +json_object * +ucv_to_json(uc_value_t *uv) +{ + uc_regexp_t *regexp; + uc_array_t *array; + json_object *jso; + size_t i; + char *s; + + switch (ucv_type(uv)) { + case UC_BOOLEAN: + return json_object_new_boolean(ucv_boolean_get(uv)); + + case UC_INTEGER: + if (ucv_is_u64(uv)) + return json_object_new_uint64(ucv_uint64_get(uv)); + + return json_object_new_int64(ucv_int64_get(uv)); + + case UC_DOUBLE: + return json_object_new_double(ucv_double_get(uv)); + + case UC_STRING: + return json_object_new_string_len(ucv_string_get(uv), ucv_string_length(uv)); + + case UC_ARRAY: + array = (uc_array_t *)uv; + jso = json_object_new_array_ext(array->count); + + for (i = 0; i < array->count; i++) + json_object_array_put_idx(jso, i, ucv_to_json(array->entries[i])); + + return jso; + + case UC_OBJECT: + jso = json_object_new_object(); + + ucv_object_foreach(uv, key, val) + json_object_object_add(jso, key, ucv_to_json(val)); + + return jso; + + case UC_REGEXP: + regexp = (uc_regexp_t *)uv; + i = asprintf(&s, "/%s/%s%s%s", + regexp->source, + regexp->global ? "g" : "", + regexp->icase ? "i" : "", + regexp->newline ? "s" : ""); + + if (i <= 0) + return NULL; + + jso = json_object_new_string_len(s, i); + + free(s); + + return jso; + + case UC_CLOSURE: + case UC_CFUNCTION: + case UC_FUNCTION: + case UC_RESSOURCE: + case UC_UPVALUE: + case UC_NULL: + return NULL; + } + + return NULL; +} + +static void +ucv_to_string_json_encoded(uc_stringbuf_t *pb, const char *s, size_t len, bool regexp) +{ + size_t i; + + if (!regexp) + ucv_stringbuf_append(pb, "\""); + + for (i = 0; s != NULL && i < len; i++, s++) { + switch (*s) { + case '"': + ucv_stringbuf_append(pb, "\\\""); + break; + + case '\\': + ucv_stringbuf_append(pb, "\\\\"); + break; + + case '\b': + ucv_stringbuf_append(pb, "\\b"); + break; + + case '\f': + ucv_stringbuf_append(pb, "\\f"); + break; + + case '\n': + ucv_stringbuf_append(pb, "\\n"); + break; + + case '\r': + ucv_stringbuf_append(pb, "\\r"); + break; + + case '\t': + ucv_stringbuf_append(pb, "\\t"); + break; + + case '/': + if (regexp) + ucv_stringbuf_append(pb, "\\"); + + ucv_stringbuf_append(pb, "/"); + break; + + default: + if (*s < 0x20) + ucv_stringbuf_printf(pb, "\\u%04x", *s); + else + ucv_stringbuf_addstr(pb, s, 1); + break; + } + } + + if (!regexp) + ucv_stringbuf_append(pb, "\""); +} + +static bool +ucv_call_tostring(uc_vm *vm, uc_stringbuf_t *pb, uc_value_t *uv, bool json) +{ + uc_value_t *proto = ucv_prototype_get(uv); + uc_value_t *tostr = ucv_object_get(proto, "tostring", NULL); + uc_value_t *str; + size_t l; + char *s; + + if (!ucv_is_callable(tostr)) + return false; + + uc_vm_stack_push(vm, ucv_get(uv)); + uc_vm_stack_push(vm, ucv_get(tostr)); + + if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE) + return false; + + str = uc_vm_stack_pop(vm); + + if (ucv_type(str) == UC_STRING) { + s = ucv_string_get(str); + l = ucv_string_length(str); + + if (json) + ucv_to_string_json_encoded(pb, s, l, false); + else if (s) + ucv_stringbuf_addstr(pb, s, l); + } + else if (json) { + ucv_stringbuf_append(pb, "\"\""); + } + + ucv_put(str); + + return true; +} + +void +_ucv_stringbuf_append(uc_stringbuf_t *pb, const char *str, size_t len) +{ + printbuf_memappend_fast(pb, str, (int)len); +} + +void +ucv_to_stringbuf(uc_vm *vm, uc_stringbuf_t *pb, uc_value_t *uv, bool json) +{ + uc_ressource_type_t *restype; + uc_ressource_t *ressource; + uc_cfunction_t *cfunction; + uc_function_t *function; + uc_closure_t *closure; + uc_regexp_t *regexp; + uc_value_t *argname; + uc_array_t *array; + size_t i, l; + double d; + char *s; + + if (ucv_is_marked(uv)) { + ucv_stringbuf_append(pb, "null"); + + return; + } + + if (vm != NULL && ucv_call_tostring(vm, pb, uv, json)) + return; + + ucv_set_mark(uv); + + switch (ucv_type(uv)) { + case UC_NULL: + ucv_stringbuf_append(pb, "null"); + break; + + case UC_BOOLEAN: + if (ucv_boolean_get(uv)) + ucv_stringbuf_append(pb, "true"); + else + ucv_stringbuf_append(pb, "false"); + break; + + case UC_INTEGER: + if (ucv_is_u64(uv)) + ucv_stringbuf_printf(pb, "%" PRIu64, ucv_uint64_get(uv)); + else + ucv_stringbuf_printf(pb, "%" PRId64, ucv_int64_get(uv)); + break; + + case UC_DOUBLE: + d = ucv_double_get(uv); + + if (json && isnan(d)) + ucv_stringbuf_append(pb, "\"NaN\""); + else if (json && d == INFINITY) + ucv_stringbuf_append(pb, "1e309"); + else if (json && d == -INFINITY) + ucv_stringbuf_append(pb, "-1e309"); + else if (isnan(d)) + ucv_stringbuf_append(pb, "NaN"); + else if (d == INFINITY) + ucv_stringbuf_append(pb, "Infinity"); + else if (d == -INFINITY) + ucv_stringbuf_append(pb, "-Infinity"); + else + ucv_stringbuf_printf(pb, "%g", d); + + break; + + case UC_STRING: + s = ucv_string_get(uv); + l = ucv_string_length(uv); + + if (s) { + if (json) + ucv_to_string_json_encoded(pb, s, l, false); + else + ucv_stringbuf_addstr(pb, s, l); + } + + break; + + case UC_ARRAY: + array = (uc_array_t *)uv; + + ucv_stringbuf_append(pb, "["); + + for (i = 0; i < array->count; i++) { + if (i) + ucv_stringbuf_append(pb, ", "); + else + ucv_stringbuf_append(pb, " "); + + ucv_to_stringbuf(vm, pb, array->entries[i], true); + } + + ucv_stringbuf_append(pb, " ]"); + break; + + case UC_OBJECT: + ucv_stringbuf_append(pb, "{"); + + i = 0; + ucv_object_foreach(uv, key, val) { + if (i++) + ucv_stringbuf_append(pb, ", "); + else + ucv_stringbuf_append(pb, " "); + + ucv_to_string_json_encoded(pb, key, strlen(key), false); + ucv_stringbuf_append(pb, ": "); + ucv_to_stringbuf(vm, pb, val, true); + } + + ucv_stringbuf_append(pb, " }"); + break; + + case UC_REGEXP: + regexp = (uc_regexp_t *)uv; + + if (json) + ucv_stringbuf_append(pb, "\""); + + ucv_stringbuf_append(pb, "/"); + ucv_to_string_json_encoded(pb, regexp->source, strlen(regexp->source), true); + ucv_stringbuf_append(pb, "/"); + + if (regexp->global) + ucv_stringbuf_append(pb, "g"); + + if (regexp->icase) + ucv_stringbuf_append(pb, "i"); + + if (regexp->newline) + ucv_stringbuf_append(pb, "s"); + + if (json) + ucv_stringbuf_append(pb, "\""); + + break; + + case UC_CLOSURE: + closure = (uc_closure_t *)uv; + function = closure->function; + + if (json) + ucv_stringbuf_append(pb, "\""); + + if (!closure->is_arrow) { + ucv_stringbuf_append(pb, "function"); + + if (function->name[0]) { + ucv_stringbuf_append(pb, " "); + ucv_stringbuf_addstr(pb, function->name, strlen(function->name)); + } + } + + ucv_stringbuf_append(pb, "("); + + for (i = 1; i <= function->nargs; i++) { + argname = uc_chunk_debug_get_variable(&function->chunk, i - 1, i, false); + + if (i > 1) + ucv_stringbuf_append(pb, ", "); + + if (i == function->nargs && function->vararg) + ucv_stringbuf_append(pb, "..."); + + if (argname) { + s = ucv_string_get(argname); + l = ucv_string_length(argname); + + if (s) + ucv_stringbuf_addstr(pb, s, l); + + ucv_put(argname); + + continue; + } + + ucv_stringbuf_printf(pb, "[arg%zu]", i); + } + + ucv_stringbuf_printf(pb, ")%s { ... }%s", + closure->is_arrow ? " =>" : "", + json ? "\"" : ""); + + break; + + case UC_CFUNCTION: + cfunction = (uc_cfunction_t *)uv; + + ucv_stringbuf_printf(pb, "%sfunction%s%s(...) { [native code] }%s", + json ? "\"" : "", + cfunction->name[0] ? " " : "", + cfunction->name[0] ? cfunction->name : "", + json ? "\"" : ""); + + break; + + case UC_FUNCTION: + ucv_stringbuf_printf(pb, "%s<function %p>%s", + json ? "\"" : "", + uv, + json ? "\"" : ""); + + break; + + case UC_RESSOURCE: + ressource = (uc_ressource_t *)uv; + restype = ucv_ressource_type_get(ressource->type); + + ucv_stringbuf_printf(pb, "%s<%s %p>%s", + json ? "\"" : "", + restype ? restype->name : "ressource", + ressource->data, + json ? "\"" : ""); + + break; + + case UC_UPVALUE: + ucv_stringbuf_printf(pb, "%s<upvalref %p>%s", + json ? "\"" : "", + uv, + json ? "\"" : ""); + + break; + } + + ucv_clear_mark(uv); +} + +static char * +ucv_to_string_any(uc_vm *vm, uc_value_t *uv, bool json) +{ + uc_stringbuf_t *pb = xprintbuf_new(); + char *rv; + + ucv_to_stringbuf(vm, pb, uv, json); + + rv = pb->buf; + + free(pb); + + return rv; +} + +char * +ucv_to_string(uc_vm *vm, uc_value_t *uv) +{ + return ucv_to_string_any(vm, uv, false); +} + +char * +ucv_to_jsonstring(uc_vm *vm, uc_value_t *uv) +{ + return ucv_to_string_any(vm, uv, true); +} + +bool +ucv_equal(uc_value_t *uv1, uc_value_t *uv2) +{ + uc_type_t t1 = ucv_type(uv1); + uc_type_t t2 = ucv_type(uv2); + const char *s1, *s2; + uint64_t u1, u2; + int64_t n1, n2; + bool b1, b2; + + if (t1 != t2) + return false; + + if (uv1 == uv2) + return true; + + switch (t1) { + case UC_NULL: + return true; + + case UC_BOOLEAN: + return ucv_boolean_get(uv1) == ucv_boolean_get(uv2); + + case UC_DOUBLE: + return ucv_double_get(uv1) == ucv_double_get(uv2); + + case UC_INTEGER: + n1 = ucv_int64_get(uv1); + b1 = (errno == 0); + + n2 = ucv_int64_get(uv2); + b2 = (errno == 0); + + if (b1 && b2) + return (n1 == n2); + + u1 = ucv_uint64_get(uv1); + b1 = (errno == 0); + + u2 = ucv_uint64_get(uv2); + b2 = (errno == 0); + + if (b1 && b2) + return (u1 == u2); + + return false; + + case UC_STRING: + s1 = ucv_string_get(uv1); + s2 = ucv_string_get(uv2); + u1 = ucv_string_length(uv1); + u2 = ucv_string_length(uv2); + + if (s1 == NULL || s2 == NULL || u1 != u2) + return false; + + return (memcmp(s1, s2, u1) == 0); + + case UC_ARRAY: + u1 = ucv_array_length(uv1); + u2 = ucv_array_length(uv2); + + if (u1 != u2) + return false; + + for (u1 = 0; u1 < u2; u1++) + if (!ucv_equal(ucv_array_get(uv1, u1), ucv_array_get(uv2, u1))) + return false; + + return true; + + case UC_OBJECT: + u1 = ucv_object_length(uv1); + u2 = ucv_object_length(uv2); + + if (u1 != u2) + return false; + + ucv_object_foreach(uv1, key, val) { + if (!ucv_equal(val, ucv_object_get(uv2, key, NULL))) + return false; + } + + ucv_object_foreach(uv2, key2, val2) { + (void)val2; + ucv_object_get(uv1, key2, &b1); + + if (!b1) + return false; + } + + return true; + + default: + return false; + } +} + +void +ucv_gc(uc_vm *vm, bool final) +{ + uc_weakref_t *ref, *tmp; + uc_value_t *val; + size_t i; + + if (!final) { + /* mark reachable objects */ + ucv_gc_mark(vm->globals); + + for (i = 0; i < vm->callframes.count; i++) + ucv_gc_mark(vm->callframes.entries[i].ctx); + + for (i = 0; i < vm->stack.count; i++) + ucv_gc_mark(vm->stack.entries[i]); + } + + /* unref unreachable objects */ + for (ref = vm->values.next; ref != &vm->values; ref = ref->next) { + val = (uc_value_t *)((uintptr_t)ref - offsetof(uc_array_t, ref)); + + if (ucv_is_marked(val)) + ucv_clear_mark(val); + else + ucv_free(val, true); + } + + /* free destroyed objects */ + for (ref = vm->values.next, tmp = ref->next; ref != &vm->values; ref = tmp, tmp = tmp->next) { + val = (uc_value_t *)((uintptr_t)ref - offsetof(uc_array_t, ref)); + + if (val->type == UC_NULL) { + ucv_unref(ref); + free(val); + } + } +} + + +#ifdef __GNUC__ + +__attribute__((destructor)) +static void ucv_ressource_types_free(void) +{ + size_t i; + + for (i = 0; i < res_types.count; i++) + ucv_put(res_types.entries[i].proto); + + uc_vector_clear(&res_types); +} + +#endif |