pocketlang/src/var.c

1119 lines
31 KiB
C
Raw Normal View History

2021-02-07 15:40:00 +08:00
/*
* Copyright (c) 2020-2021 Thakee Nathees
* Licensed under: MIT License
*/
2021-05-24 06:17:52 +08:00
#include "var.h"
2021-02-15 20:49:19 +08:00
2021-05-24 06:17:52 +08:00
#include <stdio.h>
2021-05-03 21:09:23 +08:00
#include "utils.h"
2021-02-07 15:40:00 +08:00
#include "vm.h"
/*****************************************************************************/
/* PUBLIC API */
/*****************************************************************************/
2021-05-23 04:59:32 +08:00
PkVarType pkGetValueType(const PkVar value) {
2021-05-23 04:59:32 +08:00
__ASSERT(value != NULL, "Given value was NULL.");
if (IS_NULL(*(const Var*)(value))) return PK_NULL;
if (IS_BOOL(*(const Var*)(value))) return PK_BOOL;
if (IS_NUM(*(const Var*)(value))) return PK_NUMBER;
2021-05-23 04:59:32 +08:00
__ASSERT(IS_OBJ(*(const Var*)(value)),
2021-05-23 04:59:32 +08:00
"Invalid var pointer. Might be a dangling pointer");
const Object* obj = AS_OBJ(*(const Var*)(value));
2021-05-23 04:59:32 +08:00
switch (obj->type) {
case OBJ_STRING: return PK_STRING;
case OBJ_LIST: return PK_LIST;
case OBJ_MAP: return PK_MAP;
case OBJ_RANGE: return PK_RANGE;
case OBJ_SCRIPT: return PK_SCRIPT;
case OBJ_FUNC: return PK_FUNCTION;
case OBJ_FIBER: return PK_FIBER;
case OBJ_USER: TODO; break;
2021-05-23 04:59:32 +08:00
}
2021-05-23 04:59:32 +08:00
UNREACHABLE();
return (PkVarType)0;
}
2021-05-23 04:59:32 +08:00
PkHandle* pkNewString(PKVM* vm, const char* value) {
return vmNewHandle(vm, VAR_OBJ(newString(vm, value)));
}
2021-05-29 02:53:46 +08:00
PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len) {
return vmNewHandle(vm, VAR_OBJ(newStringLength(vm, value, (uint32_t)len)));
2021-05-29 02:53:46 +08:00
}
2021-05-23 04:59:32 +08:00
PkHandle* pkNewList(PKVM* vm) {
return vmNewHandle(vm, VAR_OBJ(newList(vm, MIN_CAPACITY)));
}
2021-05-23 04:59:32 +08:00
PkHandle* pkNewMap(PKVM* vm) {
return vmNewHandle(vm, VAR_OBJ(newMap(vm)));
}
const char* pkStringGetData(const PkVar value) {
const Var str = (*(const Var*)value);
__ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string.");
2021-05-23 04:59:32 +08:00
return ((String*)AS_OBJ(str))->data;
}
2021-05-23 04:59:32 +08:00
/*****************************************************************************/
/* VAR INTERNALS */
/*****************************************************************************/
// Number of maximum digits for to_string buffer.
2021-02-12 01:35:43 +08:00
#define TO_STRING_BUFF_SIZE 128
2021-05-03 21:09:23 +08:00
// The maximum percentage of the map entries that can be filled before the map
// is grown. A lower percentage reduce collision which makes looks up faster
// but take more memory.
2021-05-05 12:55:27 +08:00
#define MAP_LOAD_PERCENT 75
2021-05-03 21:09:23 +08:00
// The factor a collection would grow by when it's exceeds the current
// capacity. The new capacity will be calculated by multiplying it's old
// capacity by the GROW_FACTOR.
2021-05-03 21:09:23 +08:00
#define GROW_FACTOR 2
2021-05-09 18:28:00 +08:00
void varInitObject(Object* self, PKVM* vm, ObjectType type) {
2021-02-12 01:35:43 +08:00
self->type = type;
2021-04-26 17:34:30 +08:00
self->is_marked = false;
2021-02-12 01:35:43 +08:00
self->next = vm->first;
vm->first = self;
2021-04-26 17:34:30 +08:00
}
2021-05-19 02:59:09 +08:00
void grayObject(PKVM* vm, Object* self) {
2021-04-26 17:34:30 +08:00
if (self == NULL || self->is_marked) return;
self->is_marked = true;
// Add the object to the VM's gray_list so that we can recursively mark
// it's referenced objects later.
2021-05-01 18:13:39 +08:00
if (vm->gray_list_count >= vm->gray_list_capacity) {
vm->gray_list_capacity *= 2;
vm->gray_list = (Object**)vm->config.realloc_fn(
vm->gray_list,
vm->gray_list_capacity * sizeof(Object*),
2021-04-26 17:34:30 +08:00
vm->config.user_data);
}
2021-05-01 18:13:39 +08:00
vm->gray_list[vm->gray_list_count++] = self;
2021-04-26 17:34:30 +08:00
}
2021-05-19 02:59:09 +08:00
void grayValue(PKVM* vm, Var self) {
2021-04-26 17:34:30 +08:00
if (!IS_OBJ(self)) return;
2021-05-19 02:59:09 +08:00
grayObject(vm, AS_OBJ(self));
2021-05-01 18:13:39 +08:00
}
2021-05-19 02:59:09 +08:00
void grayVarBuffer(PKVM* vm, VarBuffer* self) {
if (self == NULL) return;
2021-05-05 12:55:27 +08:00
for (uint32_t i = 0; i < self->count; i++) {
2021-05-19 02:59:09 +08:00
grayValue(vm, self->data[i]);
2021-05-01 18:13:39 +08:00
}
}
#define GRAY_OBJ_BUFFER(m_name) \
2021-05-19 02:59:09 +08:00
void gray##m_name##Buffer(PKVM* vm, m_name##Buffer* self) { \
if (self == NULL) return; \
for (uint32_t i = 0; i < self->count; i++) { \
2021-05-19 02:59:09 +08:00
grayObject(vm, &self->data[i]->_super); \
} \
2021-05-01 18:13:39 +08:00
}
GRAY_OBJ_BUFFER(String)
GRAY_OBJ_BUFFER(Function)
2021-05-01 18:13:39 +08:00
2021-05-09 18:28:00 +08:00
static void blackenObject(Object* obj, PKVM* vm) {
2021-05-01 18:13:39 +08:00
// TODO: trace here.
switch (obj->type) {
case OBJ_STRING: {
vm->bytes_allocated += sizeof(String);
2021-05-19 02:59:09 +08:00
vm->bytes_allocated += ((size_t)((String*)obj)->length + 1);
2021-05-01 18:13:39 +08:00
} break;
case OBJ_LIST: {
List* list = (List*)obj;
2021-05-19 02:59:09 +08:00
grayVarBuffer(vm, &list->elements);
2021-05-01 18:13:39 +08:00
vm->bytes_allocated += sizeof(List);
vm->bytes_allocated += sizeof(Var) * list->elements.capacity;
} break;
case OBJ_MAP: {
2021-05-05 12:55:27 +08:00
Map* map = (Map*)obj;
2021-05-07 17:41:19 +08:00
for (uint32_t i = 0; i < map->capacity; i++) {
2021-05-05 12:55:27 +08:00
if (IS_UNDEF(map->entries[i].key)) continue;
grayValue(vm, map->entries[i].key);
grayValue(vm, map->entries[i].value);
2021-05-05 12:55:27 +08:00
}
vm->bytes_allocated += sizeof(Map);
vm->bytes_allocated += sizeof(MapEntry) * map->capacity;
2021-05-01 18:13:39 +08:00
} break;
case OBJ_RANGE: {
vm->bytes_allocated += sizeof(Range);
} break;
case OBJ_SCRIPT:
{
Script* scr = (Script*)obj;
2021-05-01 18:13:39 +08:00
vm->bytes_allocated += sizeof(Script);
grayObject(vm, &scr->path->_super);
grayObject(vm, &scr->moudle->_super);
2021-05-07 17:41:19 +08:00
grayVarBuffer(vm, &scr->globals);
vm->bytes_allocated += sizeof(Var) * scr->globals.capacity;
2021-05-01 18:13:39 +08:00
// Integer buffer have no gray call.
vm->bytes_allocated += sizeof(uint32_t) * scr->global_names.capacity;
2021-05-01 18:13:39 +08:00
grayVarBuffer(vm, &scr->literals);
vm->bytes_allocated += sizeof(Var) * scr->literals.capacity;
2021-05-01 18:13:39 +08:00
grayFunctionBuffer(vm, &scr->functions);
vm->bytes_allocated += sizeof(Function*) * scr->functions.capacity;
2021-05-01 18:13:39 +08:00
// Integer buffer have no gray call.
vm->bytes_allocated += sizeof(uint32_t) * scr->function_names.capacity;
2021-05-01 18:13:39 +08:00
grayStringBuffer(vm, &scr->names);
vm->bytes_allocated += sizeof(String*) * scr->names.capacity;
2021-05-01 18:13:39 +08:00
grayObject(vm, &scr->body->_super);
2021-05-01 18:13:39 +08:00
} break;
case OBJ_FUNC:
{
Function* func = (Function*)obj;
vm->bytes_allocated += sizeof(Function);
2021-05-19 02:59:09 +08:00
grayObject(vm, &func->owner->_super);
2021-05-01 18:13:39 +08:00
if (!func->is_native) {
Fn* fn = func->fn;
vm->bytes_allocated += sizeof(uint8_t)* fn->opcodes.capacity;
vm->bytes_allocated += sizeof(int) * fn->oplines.capacity;
}
} break;
case OBJ_FIBER:
{
Fiber* fiber = (Fiber*)obj;
vm->bytes_allocated += sizeof(Fiber);
2021-05-19 02:59:09 +08:00
grayObject(vm, &fiber->func->_super);
2021-05-01 18:13:39 +08:00
// Blacken the stack.
for (Var* local = fiber->stack; local < fiber->sp; local++) {
2021-05-19 02:59:09 +08:00
grayValue(vm, *local);
2021-05-01 18:13:39 +08:00
}
vm->bytes_allocated += sizeof(Var) * fiber->stack_size;
// Blacken call frames.
for (int i = 0; i < fiber->frame_count; i++) {
grayObject(vm, (Object*)&fiber->frames[i].fn->_super);
2021-05-19 02:59:09 +08:00
grayObject(vm, &fiber->frames[i].fn->owner->_super);
2021-05-01 18:13:39 +08:00
}
vm->bytes_allocated += sizeof(CallFrame) * fiber->frame_capacity;
2021-05-19 02:59:09 +08:00
grayObject(vm, &fiber->caller->_super);
grayObject(vm, &fiber->error->_super);
2021-05-01 18:13:39 +08:00
} break;
case OBJ_USER:
TODO;
break;
}
}
2021-05-09 18:28:00 +08:00
void blackenObjects(PKVM* vm) {
2021-05-01 18:13:39 +08:00
while (vm->gray_list_count > 0) {
// Pop the gray object from the list.
Object* gray = vm->gray_list[--vm->gray_list_count];
blackenObject(gray, vm);
}
2021-02-07 15:40:00 +08:00
}
Var doubleToVar(double value) {
#if VAR_NAN_TAGGING
2021-05-03 21:09:23 +08:00
return utilDoubleToBits(value);
2021-02-07 15:40:00 +08:00
#else
2021-05-03 21:09:23 +08:00
#error TODO:
2021-02-07 15:40:00 +08:00
#endif // VAR_NAN_TAGGING
}
2021-02-11 01:23:48 +08:00
double varToDouble(Var value) {
2021-02-07 15:40:00 +08:00
#if VAR_NAN_TAGGING
2021-05-03 21:09:23 +08:00
return utilDoubleFromBits(value);
2021-02-07 15:40:00 +08:00
#else
2021-05-03 21:09:23 +08:00
#error TODO:
2021-02-07 15:40:00 +08:00
#endif // VAR_NAN_TAGGING
}
2021-05-09 18:28:00 +08:00
static String* _allocateString(PKVM* vm, size_t length) {
2021-04-28 21:56:56 +08:00
String* string = ALLOCATE_DYNAMIC(vm, String, length + 1, char);
varInitObject(&string->_super, vm, OBJ_STRING);
2021-05-01 18:13:39 +08:00
string->length = (uint32_t)length;
2021-04-28 21:56:56 +08:00
string->data[length] = '\0';
2021-05-24 06:17:52 +08:00
string->capacity = (uint32_t)(length + 1);
2021-04-28 21:56:56 +08:00
return string;
}
2021-05-09 18:28:00 +08:00
String* newStringLength(PKVM* vm, const char* text, uint32_t length) {
2021-02-07 15:40:00 +08:00
2021-02-12 01:35:43 +08:00
ASSERT(length == 0 || text != NULL, "Unexpected NULL string.");
2021-02-07 15:40:00 +08:00
2021-05-04 18:24:26 +08:00
String* string = _allocateString(vm, length);
2021-02-07 15:40:00 +08:00
2021-04-28 21:56:56 +08:00
if (length != 0 && text != NULL) memcpy(string->data, text, length);
2021-05-04 18:24:26 +08:00
string->hash = utilHashString(string->data);
2021-02-12 01:35:43 +08:00
return string;
2021-02-07 15:40:00 +08:00
}
2021-05-09 18:28:00 +08:00
List* newList(PKVM* vm, uint32_t size) {
2021-02-13 01:40:19 +08:00
List* list = ALLOCATE(vm, List);
2021-05-24 06:17:52 +08:00
vmPushTempRef(vm, &list->_super);
2021-02-13 01:40:19 +08:00
varInitObject(&list->_super, vm, OBJ_LIST);
varBufferInit(&list->elements);
if (size > 0) {
varBufferFill(&list->elements, vm, VAR_NULL, size);
list->elements.count = 0;
}
2021-05-24 06:17:52 +08:00
vmPopTempRef(vm);
2021-02-13 21:57:59 +08:00
return list;
2021-02-13 01:40:19 +08:00
}
2021-05-09 18:28:00 +08:00
Map* newMap(PKVM* vm) {
2021-05-03 21:09:23 +08:00
Map* map = ALLOCATE(vm, Map);
varInitObject(&map->_super, vm, OBJ_MAP);
map->capacity = 0;
map->count = 0;
map->entries = NULL;
return map;
}
2021-05-09 18:28:00 +08:00
Range* newRange(PKVM* vm, double from, double to) {
Range* range = ALLOCATE(vm, Range);
varInitObject(&range->_super, vm, OBJ_RANGE);
range->from = from;
range->to = to;
return range;
}
Script* newScript(PKVM* vm, String* path) {
2021-02-12 01:35:43 +08:00
Script* script = ALLOCATE(vm, Script);
varInitObject(&script->_super, vm, OBJ_SCRIPT);
script->path = path;
script->moudle = NULL;
2021-05-29 02:53:46 +08:00
script->initialized = false;
2021-02-16 02:51:00 +08:00
2021-02-12 01:35:43 +08:00
varBufferInit(&script->globals);
uintBufferInit(&script->global_names);
2021-02-12 01:35:43 +08:00
varBufferInit(&script->literals);
functionBufferInit(&script->functions);
uintBufferInit(&script->function_names);
2021-02-12 01:35:43 +08:00
stringBufferInit(&script->names);
2021-02-11 01:23:48 +08:00
2021-02-16 02:51:00 +08:00
vmPushTempRef(vm, &script->_super);
const char* fn_name = "$(SourceBody)";
2021-02-16 02:51:00 +08:00
script->body = newFunction(vm, fn_name, (int)strlen(fn_name), script, false);
vmPopTempRef(vm);
2021-02-12 01:35:43 +08:00
return script;
2021-02-07 15:40:00 +08:00
}
2021-05-09 18:28:00 +08:00
Function* newFunction(PKVM* vm, const char* name, int length, Script* owner,
2021-02-07 15:40:00 +08:00
bool is_native) {
2021-02-12 01:35:43 +08:00
Function* func = ALLOCATE(vm, Function);
varInitObject(&func->_super, vm, OBJ_FUNC);
2021-02-07 15:40:00 +08:00
2021-04-26 17:34:30 +08:00
if (owner == NULL) {
ASSERT(is_native, OOPS);
func->name = name;
func->owner = NULL;
func->is_native = is_native;
} else {
// Add the name in the script's function buffer.
vmPushTempRef(vm, &func->_super);
functionBufferWrite(&owner->functions, vm, func);
uint32_t name_index = scriptAddName(owner, vm, name, length);
uintBufferWrite(&owner->function_names, vm, name_index);
2021-04-26 17:34:30 +08:00
vmPopTempRef(vm);
func->name = owner->names.data[name_index]->data;
2021-04-26 17:34:30 +08:00
func->owner = owner;
func->arity = -2; // -1 means variadic args.
func->is_native = is_native;
}
2021-02-16 02:51:00 +08:00
2021-02-07 15:40:00 +08:00
2021-02-12 01:35:43 +08:00
if (is_native) {
func->native = NULL;
} else {
Fn* fn = ALLOCATE(vm, Fn);
2021-02-07 15:40:00 +08:00
2021-02-12 01:35:43 +08:00
byteBufferInit(&fn->opcodes);
uintBufferInit(&fn->oplines);
2021-02-12 01:35:43 +08:00
fn->stack_size = 0;
func->fn = fn;
}
return func;
2021-02-07 15:40:00 +08:00
}
2021-02-09 16:21:10 +08:00
2021-05-09 18:28:00 +08:00
Fiber* newFiber(PKVM* vm) {
2021-04-26 17:34:30 +08:00
Fiber* fiber = ALLOCATE(vm, Fiber);
memset(fiber, 0, sizeof(Fiber));
varInitObject(&fiber->_super, vm, OBJ_FIBER);
return fiber;
}
2021-05-19 02:59:09 +08:00
void listInsert(PKVM* vm, List* self, uint32_t index, Var value) {
2021-05-03 21:09:23 +08:00
// Add an empty slot at the end of the buffer.
if (IS_OBJ(value)) vmPushTempRef(vm, AS_OBJ(value));
varBufferWrite(&self->elements, vm, VAR_NULL);
if (IS_OBJ(value)) vmPopTempRef(vm);
// Shift the existing elements down.
2021-05-04 18:24:26 +08:00
for (uint32_t i = self->elements.count - 1; i > index; i--) {
2021-05-03 21:09:23 +08:00
self->elements.data[i] = self->elements.data[i - 1];
}
// Insert the new element.
self->elements.data[index] = value;
}
2021-05-19 02:59:09 +08:00
Var listRemoveAt(PKVM* vm, List* self, uint32_t index) {
2021-05-03 21:09:23 +08:00
Var removed = self->elements.data[index];
if (IS_OBJ(removed)) vmPushTempRef(vm, AS_OBJ(removed));
// Shift the rest of the elements up.
2021-05-04 18:24:26 +08:00
for (uint32_t i = index; i < self->elements.count - 1; i++) {
2021-05-03 21:09:23 +08:00
self->elements.data[i] = self->elements.data[i + 1];
}
// Shrink the size if it's too much excess.
if (self->elements.capacity / GROW_FACTOR >= self->elements.count) {
self->elements.data = (Var*)vmRealloc(vm, self->elements.data,
sizeof(Var) * self->elements.capacity,
sizeof(Var) * self->elements.capacity / GROW_FACTOR);
self->elements.capacity /= GROW_FACTOR;
}
if (IS_OBJ(removed)) vmPopTempRef(vm);
self->elements.count--;
return removed;
}
// Return a has value for the object.
static uint32_t _hashObject(Object* obj) {
ASSERT(isObjectHashable(obj->type),
"Check if it's hashable before calling this method.");
2021-05-03 21:09:23 +08:00
switch (obj->type) {
case OBJ_STRING:
2021-05-05 12:55:27 +08:00
return ((String*)obj)->hash;
2021-05-03 21:09:23 +08:00
case OBJ_LIST:
case OBJ_MAP:
goto L_unhashable;
case OBJ_RANGE:
{
Range* range = (Range*)obj;
return utilHashNumber(range->from) ^ utilHashNumber(range->to);
}
case OBJ_SCRIPT:
case OBJ_FUNC:
case OBJ_FIBER:
case OBJ_USER:
TODO;
default:
L_unhashable:
UNREACHABLE();
2021-05-03 21:09:23 +08:00
break;
}
2021-05-12 18:57:35 +08:00
UNREACHABLE();
return -1;
2021-05-03 21:09:23 +08:00
}
uint32_t varHashValue(Var v) {
if (IS_OBJ(v)) return _hashObject(AS_OBJ(v));
2021-05-03 21:09:23 +08:00
#if VAR_NAN_TAGGING
return utilHashBits(v);
2021-05-03 21:09:23 +08:00
#else
#error TODO:
#endif
}
2021-05-04 18:24:26 +08:00
// Find the entry with the [key]. Returns true if found and set [result] to
// point to the entry, return false otherwise and points [result] to where
// the entry should be inserted.
static bool _mapFindEntry(Map* self, Var key, MapEntry** result) {
// An empty map won't contain the key.
if (self->capacity == 0) return false;
// The [start_index] is where the entry supposed to be if there wasn't any
// collision occured. It'll be the start index for the linear probing.
uint32_t start_index = varHashValue(key) % self->capacity;
2021-05-04 18:24:26 +08:00
uint32_t index = start_index;
// Keep track of the first tombstone after the [start_index] if we don't find
// the key anywhere. The tombstone would be the entry at where we will have
// to insert the key/value pair.
MapEntry* tombstone = NULL;
do {
MapEntry* entry = &self->entries[index];
if (IS_UNDEF(entry->key)) {
ASSERT(IS_BOOL(entry->value), OOPS);
if (IS_TRUE(entry->value)) {
// We've found a tombstone, if we haven't found one [tombstone] should
// be updated. We still need to keep search for if the key exists.
if (tombstone == NULL) tombstone = entry;
} else {
// We've found a new empty slot and the key isn't found. If we've
// found a tombstone along the sequence we could use that entry
// otherwise the entry at the current index.
*result = (tombstone != NULL) ? tombstone : entry;
return false;
}
} else if (isValuesEqual(entry->key, key)) {
// We've found the key.
*result = entry;
return true;
}
index = (index + 1) % self->capacity;
} while (index != start_index);
// If we reach here means the map is filled with tombstone. Set the first
// tombstone as result for the next insertion and return false.
ASSERT(tombstone != NULL, OOPS);
*result = tombstone;
return false;
}
// Add the key, value pair to the entries array of the map. Returns true if
// the entry added for the first time and false for replaced vlaue.
static bool _mapInsertEntry(Map* self, Var key, Var value) {
ASSERT(self->capacity != 0, "Should ensure the capacity before inserting.");
MapEntry* result;
if (_mapFindEntry(self, key, &result)) {
// Key already found, just replace the value.
result->value = value;
return false;
} else {
result->key = key;
result->value = value;
return true;
}
}
// Resize the map's size to the given [capacity].
2021-05-19 02:59:09 +08:00
static void _mapResize(PKVM* vm, Map* self, uint32_t capacity) {
2021-05-04 18:24:26 +08:00
MapEntry* old_entries = self->entries;
uint32_t old_capacity = self->capacity;
self->entries = ALLOCATE_ARRAY(vm, MapEntry, capacity);
self->capacity = capacity;
for (uint32_t i = 0; i < capacity; i++) {
self->entries[i].key = VAR_UNDEFINED;
self->entries[i].value = VAR_FALSE;
2021-05-04 18:24:26 +08:00
}
// Insert the old entries to the new entries.
for (uint32_t i = 0; i < old_capacity; i++) {
// Skip the empty entries or tombstones.
if (IS_UNDEF(old_entries[i].key)) continue;
_mapInsertEntry(self, old_entries[i].key, old_entries[i].value);
}
DEALLOCATE(vm, old_entries);
}
2021-05-03 21:09:23 +08:00
Var mapGet(Map* self, Var key) {
2021-05-04 18:24:26 +08:00
MapEntry* entry;
if (_mapFindEntry(self, key, &entry)) return entry->value;
return VAR_UNDEFINED;
2021-05-03 21:09:23 +08:00
}
2021-05-19 02:59:09 +08:00
void mapSet(PKVM* vm, Map* self, Var key, Var value) {
2021-05-03 21:09:23 +08:00
2021-05-04 18:24:26 +08:00
// If map is about to fill, resize it first.
2021-05-05 12:55:27 +08:00
if (self->count + 1 > self->capacity * MAP_LOAD_PERCENT / 100) {
2021-05-04 18:24:26 +08:00
uint32_t capacity = self->capacity * GROW_FACTOR;
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
2021-05-19 02:59:09 +08:00
_mapResize(vm, self, capacity);
2021-05-04 18:24:26 +08:00
}
if (_mapInsertEntry(self, key, value)) {
self->count++; //< A new key added.
2021-05-03 21:09:23 +08:00
}
}
2021-05-19 02:59:09 +08:00
void mapClear(PKVM* vm, Map* self) {
2021-05-04 18:24:26 +08:00
DEALLOCATE(vm, self->entries);
self->entries = NULL;
self->capacity = 0;
self->count = 0;
2021-05-03 21:09:23 +08:00
}
2021-05-19 02:59:09 +08:00
Var mapRemoveKey(PKVM* vm, Map* self, Var key) {
2021-05-04 18:24:26 +08:00
MapEntry* entry;
if (!_mapFindEntry(self, key, &entry)) return VAR_NULL;
// Set the key as VAR_UNDEFINED to mark is as an available slow and set it's
// value to VAR_TRUE for tombstone.
Var value = entry->value;
entry->key = VAR_UNDEFINED;
entry->value = VAR_TRUE;
self->count--;
if (IS_OBJ(value)) vmPushTempRef(vm, AS_OBJ(value));
if (self->count == 0) {
// Clear the map if it's empty.
2021-05-19 02:59:09 +08:00
mapClear(vm, self);
2021-05-04 18:24:26 +08:00
} else if ((self->capacity > MIN_CAPACITY) &&
(self->capacity / (GROW_FACTOR * GROW_FACTOR)) >
((self->count * 100) / MAP_LOAD_PERCENT)) {
// We grow the map when it's filled 75% (MAP_LOAD_PERCENT) by 2
// (GROW_FACTOR) but we're not shrink the map when it's half filled (ie.
// half of the capacity is 75%). Instead we wait till it'll become 1/4 is
// filled (1/4 = 1/(GROW_FACTOR*GROW_FACTOR)) to minimize the
// reallocations and which is more faster.
uint32_t capacity = self->capacity / (GROW_FACTOR * GROW_FACTOR);
2021-05-04 18:24:26 +08:00
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
2021-05-19 02:59:09 +08:00
_mapResize(vm, self, capacity);
2021-05-04 18:24:26 +08:00
}
if (IS_OBJ(value)) vmPopTempRef(vm);
return value;
2021-05-03 21:09:23 +08:00
}
2021-05-19 02:59:09 +08:00
void freeObject(PKVM* vm, Object* self) {
2021-04-26 17:34:30 +08:00
// TODO: Debug trace memory here.
// First clean the object's referencs, but we're not recursively doallocating
// them because they're not marked and will be cleaned later.
// Example: List's `elements` is VarBuffer that contain a heap allocated
// array of `var*` which will be cleaned below but the actual `var` elements
// will won't be freed here instead they havent marked at all, and will be
// removed at the sweeping phase of the garbage collection.
2021-05-19 02:59:09 +08:00
switch (self->type) {
2021-04-26 17:34:30 +08:00
case OBJ_STRING:
break;
case OBJ_LIST:
2021-05-19 02:59:09 +08:00
varBufferClear(&(((List*)self)->elements), vm);
2021-04-26 17:34:30 +08:00
break;
case OBJ_MAP:
2021-05-19 02:59:09 +08:00
DEALLOCATE(vm, ((Map*)self)->entries);
2021-04-26 17:34:30 +08:00
break;
case OBJ_RANGE:
break;
case OBJ_SCRIPT: {
2021-05-19 02:59:09 +08:00
Script* scr = (Script*)self;
2021-04-26 17:34:30 +08:00
varBufferClear(&scr->globals, vm);
uintBufferClear(&scr->global_names, vm);
2021-04-26 17:34:30 +08:00
varBufferClear(&scr->literals, vm);
functionBufferClear(&scr->functions, vm);
uintBufferClear(&scr->function_names, vm);
2021-04-26 17:34:30 +08:00
stringBufferClear(&scr->names, vm);
} break;
2021-05-05 12:55:27 +08:00
case OBJ_FUNC: {
2021-05-19 02:59:09 +08:00
Function* func = (Function*)self;
2021-04-26 17:34:30 +08:00
if (!func->is_native) {
byteBufferClear(&func->fn->opcodes, vm);
uintBufferClear(&func->fn->oplines, vm);
2021-04-26 17:34:30 +08:00
}
} break;
2021-05-05 12:55:27 +08:00
case OBJ_FIBER: {
2021-05-19 02:59:09 +08:00
Fiber* fiber = (Fiber*)self;
2021-04-26 17:34:30 +08:00
DEALLOCATE(vm, fiber->stack);
DEALLOCATE(vm, fiber->frames);
} break;
case OBJ_USER:
2021-05-05 12:55:27 +08:00
TODO; // Remove OBJ_USER.
2021-04-26 17:34:30 +08:00
break;
}
2021-05-19 02:59:09 +08:00
DEALLOCATE(vm, self);
2021-04-26 17:34:30 +08:00
}
2021-02-11 01:23:48 +08:00
// Utility functions //////////////////////////////////////////////////////////
2021-02-09 16:21:10 +08:00
2021-05-23 04:59:32 +08:00
const char* getPkVarTypeName(PkVarType type) {
switch (type) {
case PK_NULL: return "null";
case PK_BOOL: return "bool";
case PK_NUMBER: return "number";
case PK_STRING: return "String";
case PK_LIST: return "List";
case PK_MAP: return "Map";
case PK_RANGE: return "Range";
case PK_SCRIPT: return "Script";
case PK_FUNCTION: return "Function";
case PK_FIBER: return "Fiber";
}
2021-02-16 02:51:00 +08:00
2021-05-23 04:59:32 +08:00
UNREACHABLE();
return NULL;
}
const char* getObjectTypeName(ObjectType type) {
switch (type) {
2021-02-16 02:51:00 +08:00
case OBJ_STRING: return "String";
case OBJ_LIST: return "List";
case OBJ_MAP: return "Map";
case OBJ_RANGE: return "Range";
case OBJ_SCRIPT: return "Script";
case OBJ_FUNC: return "Func";
2021-05-12 18:57:35 +08:00
case OBJ_FIBER: return "Fiber";
2021-02-16 02:51:00 +08:00
case OBJ_USER: return "UserObj";
default:
UNREACHABLE();
}
}
2021-05-23 04:59:32 +08:00
const char* varTypeName(Var v) {
if (IS_NULL(v)) return "null";
if (IS_BOOL(v)) return "bool";
if (IS_NUM(v)) return "number";
ASSERT(IS_OBJ(v), OOPS);
Object* obj = AS_OBJ(v);
return getObjectTypeName(obj->type);
}
2021-05-04 18:24:26 +08:00
bool isValuesSame(Var v1, Var v2) {
2021-02-09 16:21:10 +08:00
#if VAR_NAN_TAGGING
2021-02-12 01:35:43 +08:00
// Bit representation of each values are unique so just compare the bits.
return v1 == v2;
2021-02-09 16:21:10 +08:00
#else
2021-05-03 21:09:23 +08:00
#error TODO:
2021-02-09 16:21:10 +08:00
#endif
2021-02-11 01:23:48 +08:00
}
2021-02-12 01:35:43 +08:00
2021-05-04 18:24:26 +08:00
bool isValuesEqual(Var v1, Var v2) {
if (isValuesSame(v1, v2)) return true;
// If we reach here only heap allocated objects could be compared.
if (!IS_OBJ(v1) || !IS_OBJ(v2)) return false;
Object* o1 = AS_OBJ(v1), *o2 = AS_OBJ(v2);
if (o1->type != o2->type) return false;
switch (o1->type) {
case OBJ_RANGE:
return ((Range*)o1)->from == ((Range*)o2)->from &&
((Range*)o1)->to == ((Range*)o2)->to;
case OBJ_STRING: {
String* s1 = (String*)o1, *s2 = (String*)o2;
return s1->hash == s2->hash &&
s1->length == s2->length &&
memcmp(s1->data, s2->data, s1->length) == 0;
}
case OBJ_LIST: {
/*
* l1 = []; list_append(l1, l1) # [[...]]
* l2 = []; list_append(l2, l2) # [[...]]
* l1 == l2 ## This will cause a stack overflow but not handling that
* ## (in python too).
*/
List *l1 = (List*)o1, *l2 = (List*)o2;
if (l1->elements.count != l2->elements.count) return false;
Var* v1 = l1->elements.data;
Var* v2 = l2->elements.data;
for (uint32_t i = 0; i < l1->elements.count; i++) {
if (!isValuesEqual(*v1, *v2)) return false;
v1++, v2++;
}
return true;
}
2021-05-04 18:24:26 +08:00
default:
return false;
}
}
bool isObjectHashable(ObjectType type) {
// Only list and map are un-hashable.
return type != OBJ_LIST && type != OBJ_MAP;
}
2021-05-24 06:17:52 +08:00
// This will prevent recursive list/map from crash when calling to_string, by
// checking if the current sequence is in the outer sequence linked list.
struct OuterSequence {
struct OuterSequence* outer;
// If false it'll be map. If we have multiple sequence we should use an enum
// here but we only ever support list and map as builtin sequence (so bool).
2021-05-24 06:17:52 +08:00
bool is_list;
union {
const List* list;
const Map* map;
2021-05-24 06:17:52 +08:00
};
};
typedef struct OuterSequence OuterSequence;
static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
OuterSequence* outer) {
2021-02-12 01:35:43 +08:00
if (IS_NULL(v)) {
2021-05-24 06:17:52 +08:00
ByteBufferAddString(buff, vm, "null", 4);
return;
2021-02-12 01:35:43 +08:00
} else if (IS_BOOL(v)) {
2021-05-24 06:17:52 +08:00
if (AS_BOOL(v)) ByteBufferAddString(buff, vm, "true", 4);
else ByteBufferAddString(buff, vm, "false", 5);
return;
2021-02-12 01:35:43 +08:00
} else if (IS_NUM(v)) {
2021-05-24 06:17:52 +08:00
char num_buff[TO_STRING_BUFF_SIZE];
int length = sprintf(num_buff, "%.14g", AS_NUM(v));
__ASSERT(length < TO_STRING_BUFF_SIZE, "Buffer overflowed.");
ByteBufferAddString(buff, vm, num_buff, length); return;
2021-02-12 01:35:43 +08:00
} else if (IS_OBJ(v)) {
2021-05-24 06:17:52 +08:00
const Object* obj = AS_OBJ(v);
2021-02-12 01:35:43 +08:00
switch (obj->type) {
2021-05-24 06:17:52 +08:00
2021-02-12 01:35:43 +08:00
case OBJ_STRING:
2021-02-13 01:40:19 +08:00
{
const String* str = (const String*)obj;
2021-05-24 06:17:52 +08:00
if (outer == NULL) {
ByteBufferAddString(buff, vm, str->data, str->length);
return;
}
2021-05-04 18:24:26 +08:00
// If recursive return with quotes (ex: [42, "hello", 0..10]).
2021-05-24 06:17:52 +08:00
byteBufferWrite(buff, vm, '"');
ByteBufferAddString(buff, vm, str->data, str->length);
byteBufferWrite(buff, vm, '"');
return;
2021-05-05 12:55:27 +08:00
}
case OBJ_LIST:
{
const List* list = (const List*)obj;
2021-05-24 06:17:52 +08:00
if (list->elements.count == 0) {
ByteBufferAddString(buff, vm, "[]", 2);
return;
}
2021-05-24 06:17:52 +08:00
// Check if the list is recursive.
OuterSequence* seq = outer;
while (seq != NULL) {
if (seq->is_list) {
if (seq->list == list) {
ByteBufferAddString(buff, vm, "[...]", 5);
return;
}
}
seq = seq->outer;
}
OuterSequence seq_list;
seq_list.outer = outer; seq_list.is_list = true; seq_list.list = list;
2021-05-05 12:55:27 +08:00
2021-05-24 06:17:52 +08:00
byteBufferWrite(buff, vm, '[');
2021-05-05 12:55:27 +08:00
for (uint32_t i = 0; i < list->elements.count; i++) {
2021-05-24 06:17:52 +08:00
if (i != 0) ByteBufferAddString(buff, vm, ", ", 2);
toStringInternal(vm, list->elements.data[i], buff, &seq_list);
2021-05-05 12:55:27 +08:00
}
2021-05-24 06:17:52 +08:00
byteBufferWrite(buff, vm, ']');
return;
}
case OBJ_MAP:
{
const Map* map = (const Map*)obj;
2021-05-24 06:17:52 +08:00
if (map->entries == NULL) {
ByteBufferAddString(buff, vm, "{}", 2);
return;
}
2021-05-24 06:17:52 +08:00
// Check if the map is recursive.
OuterSequence* seq = outer;
while (seq != NULL) {
if (!seq->is_list) {
if (seq->map == map) {
ByteBufferAddString(buff, vm, "{...}", 5);
return;
}
}
seq = seq->outer;
}
OuterSequence seq_map;
seq_map.outer = outer; seq_map.is_list = false; seq_map.map = map;
2021-05-24 06:17:52 +08:00
byteBufferWrite(buff, vm, '{');
uint32_t i = 0; // Index of the current entry to iterate.
bool _first = true; // For first element no ',' required.
do {
// Get the next valid key index.
bool _done = false;
while (IS_UNDEF(map->entries[i].key)) {
2021-05-24 06:17:52 +08:00
if (++i >= map->capacity) {
_done = true;
break;
}
}
if (_done) break;
2021-05-24 06:17:52 +08:00
if (!_first) {
ByteBufferAddString(buff, vm, ", ", 2);
_first = false;
}
toStringInternal(vm, map->entries[i].key, buff, &seq_map);
byteBufferWrite(buff, vm, ':');
toStringInternal(vm, map->entries[i].value, buff, &seq_map);
i++;
} while (i < map->capacity);
2021-05-24 06:17:52 +08:00
byteBufferWrite(buff, vm, '}');
return;
2021-02-13 01:40:19 +08:00
}
2021-02-12 01:35:43 +08:00
case OBJ_RANGE:
{
const Range* range = (const Range*)obj;
2021-05-24 06:17:52 +08:00
char buff_from[STR_NUM_BUFF_SIZE];
const int len_from = snprintf(buff_from, sizeof(buff_from), "%f",
range->from);
2021-05-24 06:17:52 +08:00
char buff_to[STR_NUM_BUFF_SIZE];
const int len_to = snprintf(buff_to, sizeof(buff_to), "%f", range->to);
2021-05-24 06:17:52 +08:00
ByteBufferAddString(buff, vm, "[Range:", 7);
ByteBufferAddString(buff, vm, buff_from, len_from);
ByteBufferAddString(buff, vm, "..", 2);
ByteBufferAddString(buff, vm, buff_to, len_to);
byteBufferWrite(buff, vm, ']');
return;
}
case OBJ_SCRIPT: {
const Script* scr = (const Script*)obj;
2021-05-24 06:17:52 +08:00
ByteBufferAddString(buff, vm, "[Module:", 8);
2021-05-19 02:59:09 +08:00
if (scr->moudle != NULL) {
ByteBufferAddString(buff, vm, scr->moudle->data,
scr->moudle->length);
2021-05-19 02:59:09 +08:00
} else {
2021-05-24 06:17:52 +08:00
byteBufferWrite(buff, vm, '"');
ByteBufferAddString(buff, vm, scr->path->data, scr->path->length);
byteBufferWrite(buff, vm, '"');
2021-05-19 02:59:09 +08:00
}
2021-05-24 06:17:52 +08:00
byteBufferWrite(buff, vm, ']');
return;
}
case OBJ_FUNC: {
const Function* fn = (const Function*)obj;
2021-05-24 06:17:52 +08:00
ByteBufferAddString(buff, vm, "[Func:", 6);
ByteBufferAddString(buff, vm, fn->name, (uint32_t)strlen(fn->name));
2021-05-24 06:17:52 +08:00
byteBufferWrite(buff, vm, ']');
return;
}
2021-05-24 06:17:52 +08:00
// TODO: Maybe add address with %p.
case OBJ_FIBER: ByteBufferAddString(buff, vm, "[Fiber]", 7); return;
case OBJ_USER: ByteBufferAddString(buff, vm, "[UserObj]", 9); return;
2021-02-12 01:35:43 +08:00
break;
}
}
UNREACHABLE();
2021-05-24 06:17:52 +08:00
return;
}
String* toString(PKVM* vm, Var v) {
ByteBuffer buff;
byteBufferInit(&buff);
toStringInternal(vm, v, &buff, NULL);
return newStringLength(vm, (const char*)buff.data, buff.count);
2021-02-12 01:35:43 +08:00
}
2021-02-15 20:49:19 +08:00
bool toBool(Var v) {
2021-05-05 12:55:27 +08:00
2021-02-15 20:49:19 +08:00
if (IS_BOOL(v)) return AS_BOOL(v);
if (IS_NULL(v)) return false;
2021-05-05 12:55:27 +08:00
if (IS_NUM(v)) return AS_NUM(v) != 0;
2021-02-15 20:49:19 +08:00
ASSERT(IS_OBJ(v), OOPS);
Object* o = AS_OBJ(v);
switch (o->type) {
case OBJ_STRING: return ((String*)o)->length != 0;
case OBJ_LIST: return ((List*)o)->elements.count != 0;
2021-05-05 12:55:27 +08:00
case OBJ_MAP: return ((Map*)o)->count != 0;
case OBJ_RANGE: // [[FALLTHROUGH]]
case OBJ_SCRIPT:
2021-02-15 20:49:19 +08:00
case OBJ_FUNC:
case OBJ_USER:
return true;
default:
UNREACHABLE();
}
return true;
}
2021-04-28 21:56:56 +08:00
2021-05-09 18:28:00 +08:00
String* stringFormat(PKVM* vm, const char* fmt, ...) {
2021-04-28 21:56:56 +08:00
va_list arg_list;
// Calculate the total length of the resulting string. This is required to
// determine the final string size to allocate.
va_start(arg_list, fmt);
size_t total_length = 0;
for (const char* c = fmt; *c != '\0'; c++) {
switch (*c) {
case '$':
total_length += strlen(va_arg(arg_list, const char*));
break;
case '@':
total_length += va_arg(arg_list, String*)->length;
2021-04-28 21:56:56 +08:00
break;
default:
total_length++;
}
}
va_end(arg_list);
// Now build the new string.
2021-05-04 18:24:26 +08:00
String* result = _allocateString(vm, total_length);
2021-04-28 21:56:56 +08:00
va_start(arg_list, fmt);
char* buff = result->data;
for (const char* c = fmt; *c != '\0'; c++) {
switch (*c) {
case '$':
{
const char* string = va_arg(arg_list, const char*);
size_t length = strlen(string);
memcpy(buff, string, length);
buff += length;
} break;
case '@':
{
String* string = va_arg(arg_list, String*);
2021-04-28 21:56:56 +08:00
memcpy(buff, string->data, string->length);
buff += string->length;
} break;
default:
{
*buff++ = *c;
} break;
}
}
va_end(arg_list);
2021-05-04 18:24:26 +08:00
result->hash = utilHashString(result->data);
return result;
}
2021-05-24 06:17:52 +08:00
String* stringJoin(PKVM* vm, String* str1, String* str2) {
// Optimize end case.
if (str1->length == 0) return str2;
if (str2->length == 0) return str1;
size_t length = (size_t)str1->length + (size_t)str2->length;
String* string = _allocateString(vm, length);
memcpy(string->data, str1->data, str1->length);
memcpy(string->data + str1->length, str2->data, str2->length);
// Null byte already existed. From _allocateString.
string->hash = utilHashString(string->data);
return string;
}
2021-05-09 18:28:00 +08:00
uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,
uint32_t length) {
for (uint32_t i = 0; i < self->names.count; i++) {
String* _name = self->names.data[i];
if (_name->length == length && strncmp(_name->data, name, length) == 0) {
// Name already exists in the buffer.
return i;
}
}
// If we reach here the name doesn't exists in the buffer, so add it and
// return the index.
2021-05-06 22:19:30 +08:00
String* new_name = newStringLength(vm, name, length);
vmPushTempRef(vm, &new_name->_super);
stringBufferWrite(&self->names, vm, new_name);
vmPopTempRef(vm);
return self->names.count - 1;
}
int scriptSearchFunc(Script* script, const char* name, uint32_t length) {
for (uint32_t i = 0; i < script->function_names.count; i++) {
uint32_t name_index = script->function_names.data[i];
String* fn_name = script->names.data[name_index];
if (fn_name->length == length &&
strncmp(fn_name->data, name, length) == 0) {
return i;
}
}
return -1;
}
int scriptSearchGlobals(Script* script, const char* name, uint32_t length) {
for (uint32_t i = 0; i < script->global_names.count; i++) {
uint32_t name_index = script->global_names.data[i];
String* g_name = script->names.data[name_index];
if (g_name->length == length && strncmp(g_name->data, name, length) == 0) {
return i;
}
}
return -1;
}