2021-02-11 01:23:48 +08:00
|
|
|
/*
|
2022-04-03 02:39:57 +08:00
|
|
|
* Copyright (c) 2020-2022 Thakee Nathees
|
|
|
|
* Copyright (c) 2021-2022 Pocketlang Contributors
|
2021-06-09 18:42:26 +08:00
|
|
|
* Distributed Under The MIT License
|
2021-02-11 01:23:48 +08:00
|
|
|
*/
|
|
|
|
|
2021-06-09 18:42:26 +08:00
|
|
|
#include "pk_core.h"
|
2021-02-13 01:40:19 +08:00
|
|
|
|
2021-05-16 15:05:54 +08:00
|
|
|
#include <ctype.h>
|
2021-06-24 19:17:30 +08:00
|
|
|
#include <limits.h>
|
2021-02-13 01:40:19 +08:00
|
|
|
#include <math.h>
|
2021-02-15 20:49:19 +08:00
|
|
|
#include <time.h>
|
|
|
|
|
2021-06-12 14:56:09 +08:00
|
|
|
#include "pk_debug.h"
|
2021-06-09 18:42:26 +08:00
|
|
|
#include "pk_utils.h"
|
|
|
|
#include "pk_vm.h"
|
2021-02-11 01:23:48 +08:00
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
// Returns the docstring of the function, which is a static const char* defined
|
|
|
|
// just above the function by the DEF() macro below.
|
|
|
|
#define DOCSTRING(fn) _pk_doc_##fn
|
|
|
|
|
|
|
|
// A macro to declare a function, with docstring, which is defined as
|
|
|
|
// _pk_doc_<fn> = docstring; That'll used to generate function help text.
|
2021-06-20 18:23:21 +08:00
|
|
|
#define DEF(fn, docstring) \
|
|
|
|
static const char* DOCSTRING(fn) = docstring; \
|
2021-06-18 12:42:57 +08:00
|
|
|
static void fn(PKVM* vm)
|
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
// Create a new module with the given [name] and returns as a Module* for
|
2021-06-03 04:44:01 +08:00
|
|
|
// internal. Which will be wrapped by pkNewModule to return a pkHandle*.
|
2022-04-11 16:04:22 +08:00
|
|
|
static Module* newModuleInternal(PKVM* vm, const char* name);
|
2021-06-03 04:44:01 +08:00
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
// Adds a function to the module with the give properties and add the function
|
2022-04-10 11:07:44 +08:00
|
|
|
// to the module's globals variables.
|
2022-04-11 16:04:22 +08:00
|
|
|
static void moduleAddFunctionInternal(PKVM* vm, Module* module,
|
2021-06-03 04:44:01 +08:00
|
|
|
const char* name, pkNativeFn fptr,
|
2021-06-18 12:42:57 +08:00
|
|
|
int arity, const char* docstring);
|
2021-05-23 04:59:32 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
/*****************************************************************************/
|
|
|
|
/* CORE PUBLIC API */
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
2022-04-20 12:10:54 +08:00
|
|
|
#define CHECK_NULL(name) \
|
|
|
|
ASSERT(name != NULL, "Argument " #name " was NULL.");
|
|
|
|
|
|
|
|
#define CHECK_TYPE(handle, type) \
|
|
|
|
do { \
|
|
|
|
CHECK_NULL(handle); \
|
|
|
|
ASSERT(IS_OBJ_TYPE(handle->value, type), \
|
|
|
|
"Given handle is not of type " #type "."); \
|
|
|
|
} while (false)
|
|
|
|
|
2021-05-23 04:59:32 +08:00
|
|
|
PkHandle* pkNewModule(PKVM* vm, const char* name) {
|
2022-04-20 12:10:54 +08:00
|
|
|
CHECK_NULL(name);
|
2022-04-11 16:04:22 +08:00
|
|
|
Module* module = newModuleInternal(vm, name);
|
2021-06-04 09:28:42 +08:00
|
|
|
return vmNewHandle(vm, VAR_OBJ(module));
|
2021-05-23 04:59:32 +08:00
|
|
|
}
|
|
|
|
|
2022-04-17 09:17:27 +08:00
|
|
|
void pkRegisterModule(PKVM* vm, PkHandle* module) {
|
2022-04-20 12:10:54 +08:00
|
|
|
CHECK_TYPE(module, OBJ_MODULE);
|
|
|
|
|
2022-04-17 09:17:27 +08:00
|
|
|
Module* module_ = (Module*)AS_OBJ(module->value);
|
|
|
|
vmRegisterModule(vm, module_, module_->name);
|
|
|
|
}
|
|
|
|
|
2022-04-20 19:11:57 +08:00
|
|
|
PkHandle* pkNewClass(PKVM* vm, const char* name,
|
|
|
|
PkHandle* base_class, PkHandle* module,
|
|
|
|
pkNewInstanceFn new_fn,
|
|
|
|
pkDeleteInstanceFn delete_fn) {
|
2022-04-20 17:10:08 +08:00
|
|
|
CHECK_NULL(module);
|
|
|
|
CHECK_NULL(name);
|
|
|
|
CHECK_TYPE(module, OBJ_MODULE);
|
|
|
|
|
2022-04-20 18:05:33 +08:00
|
|
|
Class* super = vm->builtin_classes[PK_OBJECT];
|
|
|
|
if (base_class != NULL) {
|
|
|
|
CHECK_TYPE(base_class, OBJ_CLASS);
|
|
|
|
super = (Class*)AS_OBJ(base_class->value);
|
|
|
|
}
|
|
|
|
|
2022-04-20 17:10:08 +08:00
|
|
|
Class* class_ = newClass(vm, name, (int)strlen(name),
|
2022-04-20 18:05:33 +08:00
|
|
|
super, (Module*)AS_OBJ(module->value),
|
|
|
|
NULL, NULL);
|
2022-04-20 19:11:57 +08:00
|
|
|
class_->new_fn = new_fn;
|
|
|
|
class_->delete_fn = delete_fn;
|
|
|
|
|
2022-04-20 17:10:08 +08:00
|
|
|
return vmNewHandle(vm, VAR_OBJ(class_));
|
|
|
|
}
|
|
|
|
|
|
|
|
void pkClassAddMethod(PKVM* vm, PkHandle* cls,
|
|
|
|
const char* name,
|
|
|
|
pkNativeFn fptr, int arity) {
|
|
|
|
CHECK_NULL(cls);
|
|
|
|
CHECK_NULL(fptr);
|
2022-04-20 19:11:57 +08:00
|
|
|
CHECK_TYPE(cls, OBJ_CLASS);
|
|
|
|
|
|
|
|
Class* class_ = (Class*)AS_OBJ(cls->value);
|
|
|
|
|
|
|
|
Function* fn = newFunction(vm, name, (int)strlen(name),
|
|
|
|
class_->owner, true, NULL, NULL);
|
|
|
|
|
|
|
|
// No need to push the function to temp references of the VM
|
|
|
|
// since it's written to the constant pool of the module and the module
|
|
|
|
// won't be garbage collected (class handle has reference to the module).
|
2022-04-20 17:10:08 +08:00
|
|
|
|
2022-04-20 19:11:57 +08:00
|
|
|
Closure* method = newClosure(vm, fn);
|
2022-04-22 00:07:38 +08:00
|
|
|
|
|
|
|
// FIXME: name "_init" is literal everywhere.
|
|
|
|
if (strcmp(name, "_init") == 0) {
|
|
|
|
class_->ctor = method;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
vmPushTempRef(vm, &method->_super); // method.
|
|
|
|
pkClosureBufferWrite(&class_->methods, vm, method);
|
|
|
|
vmPopTempRef(vm); // method.
|
|
|
|
}
|
2022-04-20 17:10:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void* pkGetSelf(const PKVM* vm) {
|
2022-04-21 09:29:47 +08:00
|
|
|
ASSERT(IS_OBJ_TYPE(vm->fiber->self, OBJ_INST), OOPS);
|
|
|
|
Instance* inst = (Instance*)AS_OBJ(vm->fiber->self);
|
2022-04-20 17:10:08 +08:00
|
|
|
ASSERT(inst->native != NULL, OOPS);
|
|
|
|
return inst->native;
|
|
|
|
}
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
void pkModuleAddGlobal(PKVM* vm, PkHandle* module,
|
2022-04-21 11:06:37 +08:00
|
|
|
const char* name, PkHandle* value) {
|
2022-04-20 12:10:54 +08:00
|
|
|
CHECK_TYPE(module, OBJ_MODULE);
|
|
|
|
CHECK_NULL(value);
|
2021-06-11 15:46:55 +08:00
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
moduleAddGlobal(vm, (Module*)AS_OBJ(module->value),
|
|
|
|
name, (uint32_t)strlen(name), value->value);
|
2021-06-11 15:46:55 +08:00
|
|
|
}
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
PkHandle* pkModuleGetGlobal(PKVM* vm, PkHandle* module, const char* name) {
|
2022-04-20 12:10:54 +08:00
|
|
|
CHECK_TYPE(module, OBJ_MODULE);
|
|
|
|
CHECK_NULL(name);
|
2022-04-15 12:19:47 +08:00
|
|
|
|
|
|
|
Module* module_ = (Module*)AS_OBJ(module->value);
|
|
|
|
int index = moduleGetGlobalIndex(module_, name, (uint32_t)strlen(name));
|
|
|
|
if (index == -1) return NULL;
|
|
|
|
return vmNewHandle(vm, module_->globals.data[index]);
|
|
|
|
}
|
|
|
|
|
2021-05-23 04:59:32 +08:00
|
|
|
void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name,
|
|
|
|
pkNativeFn fptr, int arity) {
|
2022-04-20 12:10:54 +08:00
|
|
|
CHECK_TYPE(module, OBJ_MODULE);
|
|
|
|
CHECK_NULL(fptr);
|
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
moduleAddFunctionInternal(vm, (Module*)AS_OBJ(module->value),
|
|
|
|
name, fptr, arity,
|
2021-06-18 12:42:57 +08:00
|
|
|
NULL /*TODO: Public API for function docstring.*/);
|
2021-05-23 04:59:32 +08:00
|
|
|
}
|
|
|
|
|
2022-04-20 12:10:54 +08:00
|
|
|
PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module) {
|
|
|
|
CHECK_TYPE(module, OBJ_MODULE);
|
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
Module* _module = (Module*)AS_OBJ(module->value);
|
2021-06-07 13:54:06 +08:00
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
int main_index = moduleGetGlobalIndex(_module, IMPLICIT_MAIN_NAME,
|
2022-04-10 11:07:44 +08:00
|
|
|
(uint32_t)strlen(IMPLICIT_MAIN_NAME));
|
|
|
|
if (main_index == -1) return NULL;
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT_INDEX(main_index, (int)_module->globals.count);
|
2022-04-12 04:49:09 +08:00
|
|
|
Var main_fn = _module->globals.data[main_index];
|
|
|
|
ASSERT(IS_OBJ_TYPE(main_fn, OBJ_CLOSURE), OOPS);
|
|
|
|
return vmNewHandle(vm, main_fn);
|
2022-04-07 08:43:29 +08:00
|
|
|
}
|
|
|
|
|
2021-06-10 07:35:14 +08:00
|
|
|
// A convenient macro to get the nth (1 based) argument of the current
|
|
|
|
// function.
|
2021-06-07 13:54:06 +08:00
|
|
|
#define ARG(n) (vm->fiber->ret[n])
|
2021-05-23 04:59:32 +08:00
|
|
|
|
2021-06-10 06:40:24 +08:00
|
|
|
// Evaluates to the current function's argument count.
|
2021-05-23 04:59:32 +08:00
|
|
|
#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1)
|
|
|
|
|
2021-06-03 04:44:01 +08:00
|
|
|
// Set return value for the current native function and return.
|
2021-05-23 04:59:32 +08:00
|
|
|
#define RET(value) \
|
|
|
|
do { \
|
|
|
|
*(vm->fiber->ret) = value; \
|
|
|
|
return; \
|
|
|
|
} while (false)
|
|
|
|
|
2021-06-05 22:47:59 +08:00
|
|
|
#define RET_ERR(err) \
|
|
|
|
do { \
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, err); \
|
2021-06-05 22:47:59 +08:00
|
|
|
RET(VAR_NULL); \
|
|
|
|
} while(false)
|
|
|
|
|
2021-05-23 04:59:32 +08:00
|
|
|
// Check for errors in before calling the get arg public api function.
|
2021-06-30 14:39:17 +08:00
|
|
|
#define CHECK_GET_ARG_API_ERRORS() \
|
|
|
|
do { \
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(vm->fiber != NULL, \
|
2021-06-30 14:39:17 +08:00
|
|
|
"This function can only be called at runtime."); \
|
|
|
|
if (arg != 0) {/* If Native setter, the value would be at fiber->ret */ \
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(arg > 0 && arg <= ARGC, "Invalid argument index."); \
|
2021-06-30 14:39:17 +08:00
|
|
|
} \
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(value != NULL, "Argument [value] was NULL."); \
|
2021-06-04 09:28:42 +08:00
|
|
|
} while (false)
|
2021-05-23 04:59:32 +08:00
|
|
|
|
2021-06-21 21:02:20 +08:00
|
|
|
// Set error for incompatible type provided as an argument. (TODO: got type).
|
2021-06-30 14:39:17 +08:00
|
|
|
#define ERR_INVALID_ARG_TYPE(m_type) \
|
|
|
|
do { \
|
|
|
|
if (arg != 0) { /* If Native setter, arg index would be 0. */ \
|
|
|
|
char buff[STR_INT_BUFF_SIZE]; \
|
|
|
|
sprintf(buff, "%d", arg); \
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$' at argument $.", \
|
|
|
|
m_type, buff)); \
|
|
|
|
} else { \
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$'.", m_type)); \
|
|
|
|
} \
|
2021-05-23 04:59:32 +08:00
|
|
|
} while (false)
|
|
|
|
|
2021-06-04 09:28:42 +08:00
|
|
|
int pkGetArgc(const PKVM* vm) {
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(vm->fiber != NULL, "This function can only be called at runtime.");
|
2021-05-23 04:59:32 +08:00
|
|
|
return ARGC;
|
|
|
|
}
|
|
|
|
|
2021-06-28 21:47:19 +08:00
|
|
|
bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max) {
|
|
|
|
ASSERT(min <= max, "invalid argc range (min > max).");
|
|
|
|
|
|
|
|
if (argc < min) {
|
|
|
|
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", min);
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Expected at least %s argument(s).",
|
|
|
|
buff));
|
|
|
|
return false;
|
|
|
|
|
|
|
|
} else if (argc > max) {
|
|
|
|
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", max);
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Expected at most %s argument(s).",
|
|
|
|
buff));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-06-04 09:28:42 +08:00
|
|
|
PkVar pkGetArg(const PKVM* vm, int arg) {
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(vm->fiber != NULL, "This function can only be called at runtime.");
|
|
|
|
ASSERT(arg > 0 || arg <= ARGC, "Invalid argument index.");
|
2021-05-23 04:59:32 +08:00
|
|
|
|
|
|
|
return &(ARG(arg));
|
|
|
|
}
|
|
|
|
|
2021-05-29 02:53:46 +08:00
|
|
|
bool pkGetArgBool(PKVM* vm, int arg, bool* value) {
|
|
|
|
CHECK_GET_ARG_API_ERRORS();
|
|
|
|
|
|
|
|
Var val = ARG(arg);
|
|
|
|
*value = toBool(val);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-23 04:59:32 +08:00
|
|
|
bool pkGetArgNumber(PKVM* vm, int arg, double* value) {
|
|
|
|
CHECK_GET_ARG_API_ERRORS();
|
|
|
|
|
|
|
|
Var val = ARG(arg);
|
|
|
|
if (IS_NUM(val)) {
|
|
|
|
*value = AS_NUM(val);
|
|
|
|
|
|
|
|
} else if (IS_BOOL(val)) {
|
|
|
|
*value = AS_BOOL(val) ? 1 : 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
ERR_INVALID_ARG_TYPE("number");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-06-21 21:02:20 +08:00
|
|
|
bool pkGetArgString(PKVM* vm, int arg, const char** value, uint32_t* length) {
|
2021-05-23 04:59:32 +08:00
|
|
|
CHECK_GET_ARG_API_ERRORS();
|
|
|
|
|
|
|
|
Var val = ARG(arg);
|
2021-06-04 09:28:42 +08:00
|
|
|
if (IS_OBJ_TYPE(val, OBJ_STRING)) {
|
2021-06-21 21:02:20 +08:00
|
|
|
String* str = (String*)AS_OBJ(val);
|
|
|
|
*value = str->data;
|
|
|
|
if (length) *length = str->length;
|
2021-05-29 02:53:46 +08:00
|
|
|
|
|
|
|
} else {
|
|
|
|
ERR_INVALID_ARG_TYPE("string");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-05-23 04:59:32 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) {
|
|
|
|
CHECK_GET_ARG_API_ERRORS();
|
|
|
|
|
|
|
|
Var val = ARG(arg);
|
|
|
|
if (pkGetValueType((PkVar)&val) != type) {
|
2021-06-06 05:51:18 +08:00
|
|
|
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", arg);
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Expected a $ at argument $.",
|
|
|
|
getPkVarTypeName(type), buff));
|
2021-05-23 04:59:32 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
*value = (PkVar)&val;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pkReturnNull(PKVM* vm) {
|
|
|
|
RET(VAR_NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void pkReturnBool(PKVM* vm, bool value) {
|
|
|
|
RET(VAR_BOOL(value));
|
|
|
|
}
|
|
|
|
|
|
|
|
void pkReturnNumber(PKVM* vm, double value) {
|
|
|
|
RET(VAR_NUM(value));
|
|
|
|
}
|
|
|
|
|
2021-05-29 02:53:46 +08:00
|
|
|
void pkReturnString(PKVM* vm, const char* value) {
|
2021-06-04 09:28:42 +08:00
|
|
|
RET(VAR_OBJ(newString(vm, value)));
|
2021-05-29 02:53:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void pkReturnStringLength(PKVM* vm, const char* value, size_t length) {
|
2021-06-04 09:28:42 +08:00
|
|
|
RET(VAR_OBJ(newStringLength(vm, value, (uint32_t)length)));
|
2021-05-29 02:53:46 +08:00
|
|
|
}
|
|
|
|
|
2021-05-23 04:59:32 +08:00
|
|
|
void pkReturnValue(PKVM* vm, PkVar value) {
|
|
|
|
RET(*(Var*)value);
|
|
|
|
}
|
|
|
|
|
2021-06-21 21:02:20 +08:00
|
|
|
void pkReturnHandle(PKVM* vm, PkHandle* handle) {
|
|
|
|
RET(handle->value);
|
|
|
|
}
|
|
|
|
|
2021-06-08 00:56:56 +08:00
|
|
|
const char* pkStringGetData(const PkVar value) {
|
|
|
|
const Var str = (*(const Var*)value);
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string.");
|
2021-06-08 00:56:56 +08:00
|
|
|
return ((String*)AS_OBJ(str))->data;
|
|
|
|
}
|
|
|
|
|
|
|
|
PkVar pkFiberGetReturnValue(const PkHandle* fiber) {
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(fiber != NULL, "Handle fiber was NULL.");
|
2021-06-08 00:56:56 +08:00
|
|
|
Var fb = fiber->value;
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber");
|
2021-06-08 00:56:56 +08:00
|
|
|
Fiber* _fiber = (Fiber*)AS_OBJ(fb);
|
|
|
|
return (PkVar)_fiber->ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool pkFiberIsDone(const PkHandle* fiber) {
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(fiber != NULL, "Handle fiber was NULL.");
|
2021-06-08 00:56:56 +08:00
|
|
|
Var fb = fiber->value;
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber");
|
2021-06-08 00:56:56 +08:00
|
|
|
Fiber* _fiber = (Fiber*)AS_OBJ(fb);
|
|
|
|
return _fiber->state == FIBER_DONE;
|
|
|
|
}
|
|
|
|
|
2022-04-20 12:10:54 +08:00
|
|
|
#undef CHECK_NULL
|
|
|
|
#undef CHECK_TYPE
|
|
|
|
|
2021-02-16 02:51:00 +08:00
|
|
|
/*****************************************************************************/
|
|
|
|
/* VALIDATORS */
|
|
|
|
/*****************************************************************************/
|
2021-02-13 01:40:19 +08:00
|
|
|
|
2021-06-28 21:47:19 +08:00
|
|
|
// Evaluated to true of the [num] is in byte range.
|
|
|
|
#define IS_NUM_BYTE(num) ((CHAR_MIN <= (num)) && ((num) <= CHAR_MAX))
|
|
|
|
|
2021-06-11 15:46:55 +08:00
|
|
|
// Check if [var] is a numeric value (bool/number) and set [value].
|
2021-02-15 20:49:19 +08:00
|
|
|
static inline bool isNumeric(Var var, double* value) {
|
2021-02-13 01:40:19 +08:00
|
|
|
if (IS_NUM(var)) {
|
|
|
|
*value = AS_NUM(var);
|
|
|
|
return true;
|
|
|
|
}
|
2021-06-06 22:18:47 +08:00
|
|
|
if (IS_BOOL(var)) {
|
|
|
|
*value = AS_BOOL(var);
|
|
|
|
return true;
|
|
|
|
}
|
2021-02-13 01:40:19 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-11 15:46:55 +08:00
|
|
|
// Check if [var] is a numeric value (bool/number) and set [value].
|
|
|
|
static inline bool isInteger(Var var, int64_t* value) {
|
2021-02-13 01:40:19 +08:00
|
|
|
double number;
|
|
|
|
if (isNumeric(var, &number)) {
|
2021-06-10 07:35:14 +08:00
|
|
|
// TODO: check if the number is larger for a 64 bit integer.
|
2021-06-16 02:54:30 +08:00
|
|
|
if (floor(number) == number) {
|
|
|
|
ASSERT(INT64_MIN <= number && number <= INT64_MAX,
|
|
|
|
"TODO: Large numbers haven't handled yet. Please report!");
|
|
|
|
*value = (int64_t)(number);
|
2021-02-13 01:40:19 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2021-06-11 15:46:55 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-06 11:26:51 +08:00
|
|
|
// Check if [var] is bool/number. If not, it'll set error and return false.
|
2021-06-11 15:46:55 +08:00
|
|
|
static inline bool validateNumeric(PKVM* vm, Var var, double* value,
|
|
|
|
const char* name) {
|
|
|
|
if (isNumeric(var, value)) return true;
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ must be a numeric value.", name));
|
|
|
|
return false;
|
|
|
|
}
|
2021-02-13 01:40:19 +08:00
|
|
|
|
2022-04-06 11:26:51 +08:00
|
|
|
// Check if [var] is 32 bit integer. If not, it'll set error and return false.
|
2021-06-11 15:46:55 +08:00
|
|
|
static inline bool validateInteger(PKVM* vm, Var var, int64_t* value,
|
|
|
|
const char* name) {
|
|
|
|
if (isInteger(var, value)) return true;
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ must be a whole number.", name));
|
2021-02-13 01:40:19 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-10 07:35:14 +08:00
|
|
|
// Index is could be larger than 32 bit integer, but the size in pocketlang
|
|
|
|
// limited to 32 unsigned bit integer
|
|
|
|
static inline bool validateIndex(PKVM* vm, int64_t index, uint32_t size,
|
|
|
|
const char* container) {
|
2021-02-13 01:40:19 +08:00
|
|
|
if (index < 0 || size <= index) {
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ index out of bound.", container));
|
2021-02-13 01:40:19 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-04-06 11:26:51 +08:00
|
|
|
// Check if the [condition] is true. If not, it'll set an error and return
|
|
|
|
// false.
|
|
|
|
static inline bool validateCond(PKVM* vm, bool condition, const char* err) {
|
|
|
|
if (!condition) {
|
|
|
|
VM_SET_ERROR(vm, newString(vm, err));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-24 01:55:04 +08:00
|
|
|
// Check if [var] is string for argument at [arg]. If not set error and
|
2021-05-16 15:05:54 +08:00
|
|
|
// return false.
|
2021-05-24 01:55:04 +08:00
|
|
|
#define VALIDATE_ARG_OBJ(m_class, m_type, m_name) \
|
|
|
|
static bool validateArg##m_class(PKVM* vm, int arg, m_class** value) { \
|
|
|
|
Var var = ARG(arg); \
|
|
|
|
ASSERT(arg > 0 && arg <= ARGC, OOPS); \
|
|
|
|
if (!IS_OBJ(var) || AS_OBJ(var)->type != m_type) { \
|
|
|
|
char buff[12]; sprintf(buff, "%d", arg); \
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Expected a " m_name \
|
|
|
|
" at argument $.", buff, false)); \
|
2022-04-01 01:27:53 +08:00
|
|
|
return false; \
|
2021-05-24 01:55:04 +08:00
|
|
|
} \
|
|
|
|
*value = (m_class*)AS_OBJ(var); \
|
|
|
|
return true; \
|
|
|
|
}
|
|
|
|
VALIDATE_ARG_OBJ(String, OBJ_STRING, "string")
|
|
|
|
VALIDATE_ARG_OBJ(List, OBJ_LIST, "list")
|
|
|
|
VALIDATE_ARG_OBJ(Map, OBJ_MAP, "map")
|
2022-04-09 02:53:12 +08:00
|
|
|
VALIDATE_ARG_OBJ(Closure, OBJ_CLOSURE, "closure")
|
2021-06-05 22:47:59 +08:00
|
|
|
VALIDATE_ARG_OBJ(Fiber, OBJ_FIBER, "fiber")
|
2022-04-03 19:29:03 +08:00
|
|
|
VALIDATE_ARG_OBJ(Class, OBJ_CLASS, "class")
|
2021-05-16 15:05:54 +08:00
|
|
|
|
2021-02-16 02:51:00 +08:00
|
|
|
/*****************************************************************************/
|
2021-06-07 13:54:06 +08:00
|
|
|
/* SHARED FUNCTIONS */
|
2021-02-16 02:51:00 +08:00
|
|
|
/*****************************************************************************/
|
2021-02-13 01:40:19 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
static void initializeBuiltinFunctions(PKVM* vm);
|
|
|
|
static void initializeCoreModules(PKVM* vm);
|
2022-04-17 09:17:27 +08:00
|
|
|
static void initializePrimitiveClasses(PKVM* vm);
|
2022-04-15 12:19:47 +08:00
|
|
|
|
|
|
|
void initializeCore(PKVM* vm) {
|
|
|
|
initializeBuiltinFunctions(vm);
|
|
|
|
initializeCoreModules(vm);
|
2022-04-17 09:17:27 +08:00
|
|
|
initializePrimitiveClasses(vm);
|
2021-05-07 17:41:19 +08:00
|
|
|
}
|
|
|
|
|
2021-05-15 18:46:55 +08:00
|
|
|
/*****************************************************************************/
|
|
|
|
/* CORE BUILTIN FUNCTIONS */
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(coreTypeName,
|
|
|
|
"type_name(value:var) -> string\n"
|
|
|
|
"Returns the type name of the of the value.") {
|
|
|
|
|
|
|
|
RET(VAR_OBJ(newString(vm, varTypeName(ARG(1)))));
|
|
|
|
}
|
|
|
|
|
|
|
|
DEF(coreHelp,
|
2022-04-12 04:49:09 +08:00
|
|
|
"help([fn:Closure]) -> null\n"
|
2021-06-18 12:42:57 +08:00
|
|
|
"This will write an error message to stdout and return null.") {
|
|
|
|
|
|
|
|
int argc = ARGC;
|
|
|
|
if (argc != 0 && argc != 1) {
|
|
|
|
RET_ERR(newString(vm, "Invalid argument count."));
|
2021-02-12 14:53:52 +08:00
|
|
|
}
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
if (argc == 0) {
|
|
|
|
// If there ins't an io function callback, we're done.
|
2022-04-22 20:21:17 +08:00
|
|
|
if (vm->config.stdout_write == NULL) RET(VAR_NULL);
|
|
|
|
vm->config.stdout_write(vm, "TODO: print help here\n");
|
2021-06-18 12:42:57 +08:00
|
|
|
|
|
|
|
} else if (argc == 1) {
|
2022-04-01 01:27:53 +08:00
|
|
|
|
|
|
|
// TODO: Extend help() to work with modules and classes.
|
|
|
|
// Add docstring (like python) to support it in pocketlang.
|
|
|
|
|
2022-04-12 04:49:09 +08:00
|
|
|
Closure* closure;
|
|
|
|
if (!validateArgClosure(vm, 1, &closure)) return;
|
2021-06-18 12:42:57 +08:00
|
|
|
|
|
|
|
// If there ins't an io function callback, we're done.
|
2022-04-22 20:21:17 +08:00
|
|
|
if (vm->config.stdout_write == NULL) RET(VAR_NULL);
|
2021-06-18 12:42:57 +08:00
|
|
|
|
2022-04-12 04:49:09 +08:00
|
|
|
if (closure->fn->docstring != NULL) {
|
2022-04-22 20:21:17 +08:00
|
|
|
vm->config.stdout_write(vm, closure->fn->docstring);
|
|
|
|
vm->config.stdout_write(vm, "\n\n");
|
2021-06-18 12:42:57 +08:00
|
|
|
} else {
|
2022-04-22 20:21:17 +08:00
|
|
|
vm->config.stdout_write(vm, "function '");
|
|
|
|
vm->config.stdout_write(vm, closure->fn->name);
|
|
|
|
vm->config.stdout_write(vm, "()' doesn't have a docstring.\n");
|
2021-06-18 12:42:57 +08:00
|
|
|
}
|
2021-02-12 14:53:52 +08:00
|
|
|
}
|
2021-06-18 12:42:57 +08:00
|
|
|
}
|
2021-02-12 14:53:52 +08:00
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(coreAssert,
|
|
|
|
"assert(condition:bool [, msg:string]) -> void\n"
|
|
|
|
"If the condition is false it'll terminate the current fiber with the "
|
|
|
|
"optional error message") {
|
2021-02-12 14:53:52 +08:00
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
int argc = ARGC;
|
|
|
|
if (argc != 1 && argc != 2) {
|
|
|
|
RET_ERR(newString(vm, "Invalid argument count."));
|
|
|
|
}
|
2021-02-12 14:53:52 +08:00
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
if (!toBool(ARG(1))) {
|
|
|
|
String* msg = NULL;
|
|
|
|
|
|
|
|
if (argc == 2) {
|
|
|
|
if (AS_OBJ(ARG(2))->type != OBJ_STRING) {
|
|
|
|
msg = toString(vm, ARG(2));
|
|
|
|
} else {
|
|
|
|
msg = (String*)AS_OBJ(ARG(2));
|
|
|
|
}
|
|
|
|
vmPushTempRef(vm, &msg->_super);
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Assertion failed: '@'.", msg));
|
|
|
|
vmPopTempRef(vm);
|
|
|
|
} else {
|
|
|
|
VM_SET_ERROR(vm, newString(vm, "Assertion failed."));
|
|
|
|
}
|
|
|
|
}
|
2021-06-10 07:35:14 +08:00
|
|
|
}
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(coreBin,
|
2021-06-10 07:35:14 +08:00
|
|
|
"bin(value:num) -> string\n"
|
2022-04-22 00:07:38 +08:00
|
|
|
"Returns as a binary value string with '0b' prefix.") {
|
2021-06-18 12:42:57 +08:00
|
|
|
|
2021-06-10 07:35:14 +08:00
|
|
|
int64_t value;
|
|
|
|
if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return;
|
|
|
|
|
|
|
|
char buff[STR_BIN_BUFF_SIZE];
|
|
|
|
|
2021-06-16 02:54:30 +08:00
|
|
|
bool negative = (value < 0) ? true : false;
|
|
|
|
if (negative) value = -value;
|
|
|
|
|
|
|
|
char* ptr = buff + STR_BIN_BUFF_SIZE - 1;
|
|
|
|
*ptr-- = '\0'; // NULL byte at the end of the string.
|
|
|
|
|
|
|
|
if (value != 0) {
|
|
|
|
while (value > 0) {
|
|
|
|
*ptr-- = '0' + (value & 1);
|
|
|
|
value >>= 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*ptr-- = '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
*ptr-- = 'b'; *ptr-- = '0';
|
|
|
|
if (negative) *ptr-- = '-';
|
2021-06-10 07:35:14 +08:00
|
|
|
|
2021-06-16 02:54:30 +08:00
|
|
|
uint32_t length = (uint32_t)((buff + STR_BIN_BUFF_SIZE - 1) - (ptr + 1));
|
|
|
|
RET(VAR_OBJ(newStringLength(vm, ptr + 1, length)));
|
2021-06-10 07:35:14 +08:00
|
|
|
}
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(coreHex,
|
2021-06-10 07:35:14 +08:00
|
|
|
"hex(value:num) -> string\n"
|
2021-06-18 12:42:57 +08:00
|
|
|
"Returns as a hexadecimal value string with '0x' prefix.") {
|
|
|
|
|
2021-06-10 07:35:14 +08:00
|
|
|
int64_t value;
|
|
|
|
if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return;
|
|
|
|
|
|
|
|
char buff[STR_HEX_BUFF_SIZE];
|
|
|
|
|
|
|
|
char* ptr = buff;
|
|
|
|
if (value < 0) *ptr++ = '-';
|
|
|
|
*ptr++ = '0'; *ptr++ = 'x';
|
|
|
|
|
|
|
|
if (value > UINT32_MAX || value < -(int64_t)(UINT32_MAX)) {
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, newString(vm, "Integer is too large."));
|
2021-06-10 07:35:14 +08:00
|
|
|
RET(VAR_NULL);
|
|
|
|
}
|
|
|
|
|
2022-03-31 01:50:44 +08:00
|
|
|
// TODO: sprintf limits only to 8 character hex value, we need to do it
|
2021-06-16 02:54:30 +08:00
|
|
|
// outself for a maximum of 16 character long (see bin() for reference).
|
2021-06-10 07:35:14 +08:00
|
|
|
uint32_t _x = (uint32_t)((value < 0) ? -value : value);
|
|
|
|
int length = sprintf(ptr, "%x", _x);
|
|
|
|
|
|
|
|
RET(VAR_OBJ(newStringLength(vm, buff,
|
2021-06-18 12:42:57 +08:00
|
|
|
(uint32_t)((ptr + length) - (char*)(buff)))));
|
2021-05-24 01:55:04 +08:00
|
|
|
}
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(coreYield,
|
2021-06-05 22:47:59 +08:00
|
|
|
"yield([value]) -> var\n"
|
|
|
|
"Return the current function with the yield [value] to current running "
|
|
|
|
"fiber. If the fiber is resumed, it'll run from the next statement of the "
|
|
|
|
"yield() call. If the fiber resumed with with a value, the return value of "
|
2021-06-18 12:42:57 +08:00
|
|
|
"the yield() would be that value otherwise null.") {
|
2021-06-05 22:47:59 +08:00
|
|
|
|
|
|
|
int argc = ARGC;
|
|
|
|
if (argc > 1) { // yield() or yield(val).
|
|
|
|
RET_ERR(newString(vm, "Invalid argument count."));
|
|
|
|
}
|
|
|
|
|
2021-06-10 07:35:14 +08:00
|
|
|
vmYieldFiber(vm, (argc == 1) ? &ARG(1) : NULL);
|
2021-06-05 22:47:59 +08:00
|
|
|
}
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(coreToString,
|
2021-05-24 06:17:52 +08:00
|
|
|
"to_string(value:var) -> string\n"
|
2021-06-18 12:42:57 +08:00
|
|
|
"Returns the string representation of the value.") {
|
|
|
|
|
2021-06-10 07:35:14 +08:00
|
|
|
RET(VAR_OBJ(toString(vm, ARG(1))));
|
2021-02-12 01:35:43 +08:00
|
|
|
}
|
|
|
|
|
2022-04-22 00:07:38 +08:00
|
|
|
DEF(coreChr,
|
|
|
|
"chr(value:num) -> string\n"
|
|
|
|
"Returns the ASCII string value of the integer argument.") {
|
|
|
|
|
|
|
|
int64_t num;
|
|
|
|
if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return;
|
|
|
|
|
|
|
|
if (!IS_NUM_BYTE(num)) {
|
|
|
|
RET_ERR(newString(vm, "The number is not in a byte range."));
|
|
|
|
}
|
|
|
|
|
|
|
|
char c = (char)num;
|
|
|
|
RET(VAR_OBJ(newStringLength(vm, &c, 1)));
|
|
|
|
}
|
|
|
|
|
|
|
|
DEF(coreOrd,
|
|
|
|
"ord(value:string) -> num\n"
|
|
|
|
"Returns integer value of the given ASCII character.") {
|
|
|
|
|
|
|
|
String* c;
|
|
|
|
if (!validateArgString(vm, 1, &c)) return;
|
|
|
|
if (c->length != 1) {
|
|
|
|
RET_ERR(newString(vm, "Expected a string of length 1."));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
RET(VAR_NUM((double)c->data[0]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(corePrint,
|
2021-05-24 06:17:52 +08:00
|
|
|
"print(...) -> void\n"
|
2021-06-18 12:42:57 +08:00
|
|
|
"Write each argument as space seperated, to the stdout and ends with a "
|
|
|
|
"newline.") {
|
|
|
|
|
2021-06-15 15:37:49 +08:00
|
|
|
// If the host application doesn't provide any write function, discard the
|
2021-05-14 18:44:57 +08:00
|
|
|
// output.
|
2022-04-22 20:21:17 +08:00
|
|
|
if (vm->config.stdout_write == NULL) return;
|
2021-05-14 18:44:57 +08:00
|
|
|
|
2021-02-13 01:40:19 +08:00
|
|
|
for (int i = 1; i <= ARGC; i++) {
|
2022-04-22 20:21:17 +08:00
|
|
|
if (i != 1) vm->config.stdout_write(vm, " ");
|
|
|
|
vm->config.stdout_write(vm, toString(vm, ARG(i))->data);
|
2021-02-12 01:35:43 +08:00
|
|
|
}
|
2021-02-13 01:40:19 +08:00
|
|
|
|
2022-04-22 20:21:17 +08:00
|
|
|
vm->config.stdout_write(vm, "\n");
|
2021-02-12 01:35:43 +08:00
|
|
|
}
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(coreInput,
|
2021-06-07 13:54:06 +08:00
|
|
|
"input([msg:var]) -> string\n"
|
|
|
|
"Read a line from stdin and returns it without the line ending. Accepting "
|
2021-06-18 12:42:57 +08:00
|
|
|
"an optional argument [msg] and prints it before reading.") {
|
|
|
|
|
2021-06-07 13:54:06 +08:00
|
|
|
int argc = ARGC;
|
|
|
|
if (argc != 1 && argc != 2) {
|
|
|
|
RET_ERR(newString(vm, "Invalid argument count."));
|
|
|
|
}
|
|
|
|
|
2021-06-15 15:37:49 +08:00
|
|
|
// If the host application doesn't provide any write function, return.
|
2022-04-22 20:21:17 +08:00
|
|
|
if (vm->config.stdin_read == NULL) return;
|
2021-06-07 13:54:06 +08:00
|
|
|
|
|
|
|
if (argc == 1) {
|
2022-04-22 20:21:17 +08:00
|
|
|
vm->config.stdout_write(vm, toString(vm, ARG(1))->data);
|
2021-06-07 13:54:06 +08:00
|
|
|
}
|
|
|
|
|
2022-04-22 20:21:17 +08:00
|
|
|
PkStringPtr result = vm->config.stdin_read(vm);
|
2021-06-07 13:54:06 +08:00
|
|
|
String* line = newString(vm, result.string);
|
|
|
|
if (result.on_done) result.on_done(vm, result);
|
|
|
|
RET(VAR_OBJ(line));
|
|
|
|
}
|
|
|
|
|
2021-06-27 00:12:54 +08:00
|
|
|
DEF(coreExit,
|
2021-06-28 21:47:19 +08:00
|
|
|
"exit([value:num]) -> null\n"
|
|
|
|
"Exit the process with an optional exit code provided by the argument "
|
|
|
|
"[value]. The default exit code is would be 0.") {
|
2021-06-27 00:12:54 +08:00
|
|
|
|
|
|
|
int argc = ARGC;
|
|
|
|
if (argc > 1) { // exit() or exit(val).
|
|
|
|
RET_ERR(newString(vm, "Invalid argument count."));
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t value = 0;
|
|
|
|
if (argc == 1) {
|
|
|
|
if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: this actually needs to be the VM fiber being set to null though.
|
2021-06-28 21:47:19 +08:00
|
|
|
exit((int)value);
|
2021-06-27 00:12:54 +08:00
|
|
|
}
|
|
|
|
|
2021-05-24 01:55:04 +08:00
|
|
|
// String functions.
|
|
|
|
// -----------------
|
2021-05-16 15:05:54 +08:00
|
|
|
|
2021-06-29 20:48:41 +08:00
|
|
|
DEF(coreStrSub,
|
|
|
|
"str_sub(str:string, pos:num, len:num) -> string\n"
|
|
|
|
"Returns a substring from a given string supplied. In addition, "
|
|
|
|
"the position and length of the substring are provided when this "
|
|
|
|
"function is called. For example: `str_sub(str, pos, len)`.") {
|
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
String* str;
|
|
|
|
int64_t pos, len;
|
2021-06-29 20:48:41 +08:00
|
|
|
|
|
|
|
if (!validateArgString(vm, 1, &str)) return;
|
|
|
|
if (!validateInteger(vm, ARG(2), &pos, "Argument 2")) return;
|
|
|
|
if (!validateInteger(vm, ARG(3), &len, "Argument 3")) return;
|
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
if (pos < 0 || str->length < pos)
|
2021-06-29 20:48:41 +08:00
|
|
|
RET_ERR(newString(vm, "Index out of range."));
|
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
if (str->length < pos + len)
|
|
|
|
RET_ERR(newString(vm, "Substring length exceeded the limit."));
|
|
|
|
|
2021-06-29 20:48:41 +08:00
|
|
|
// Edge case, empty string.
|
|
|
|
if (len == 0) RET(VAR_OBJ(newStringLength(vm, "", 0)));
|
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
RET(VAR_OBJ(newStringLength(vm, str->data + pos, (uint32_t)len)));
|
2021-06-29 20:48:41 +08:00
|
|
|
}
|
2021-05-16 15:05:54 +08:00
|
|
|
|
2021-05-24 01:55:04 +08:00
|
|
|
// List functions.
|
|
|
|
// ---------------
|
2021-06-03 04:44:01 +08:00
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(coreListAppend,
|
2021-06-03 04:44:01 +08:00
|
|
|
"list_append(self:List, value:var) -> List\n"
|
2021-06-18 12:42:57 +08:00
|
|
|
"Append the [value] to the list [self] and return the list.") {
|
|
|
|
|
2021-05-24 01:55:04 +08:00
|
|
|
List* list;
|
|
|
|
if (!validateArgList(vm, 1, &list)) return;
|
|
|
|
Var elem = ARG(2);
|
|
|
|
|
2021-06-11 15:46:55 +08:00
|
|
|
listAppend(vm, list, elem);
|
2021-06-04 09:28:42 +08:00
|
|
|
RET(VAR_OBJ(list));
|
2021-05-24 01:55:04 +08:00
|
|
|
}
|
|
|
|
|
2022-04-05 10:55:32 +08:00
|
|
|
// TODO: currently it takes one argument (to test string interpolation).
|
|
|
|
// Add join delimeter as an optional argument.
|
|
|
|
DEF(coreListJoin,
|
|
|
|
"list_join(self:List) -> String\n"
|
|
|
|
"Concatinate the elements of the list and return as a string.") {
|
|
|
|
|
|
|
|
List* list;
|
|
|
|
if (!validateArgList(vm, 1, &list)) return;
|
|
|
|
|
|
|
|
pkByteBuffer buff;
|
|
|
|
pkByteBufferInit(&buff);
|
|
|
|
|
2022-04-06 11:26:51 +08:00
|
|
|
for (uint32_t i = 0; i < list->elements.count; i++) {
|
2022-04-05 10:55:32 +08:00
|
|
|
String* elem = toString(vm, list->elements.data[i]);
|
|
|
|
vmPushTempRef(vm, &elem->_super); // elem
|
|
|
|
pkByteBufferAddString(&buff, vm, elem->data, elem->length);
|
2022-04-06 11:26:51 +08:00
|
|
|
vmPopTempRef(vm); // elem
|
2022-04-05 10:55:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
String* str = newStringLength(vm, (const char*)buff.data, buff.count);
|
|
|
|
pkByteBufferClear(&buff, vm);
|
|
|
|
RET(VAR_OBJ(str));
|
|
|
|
}
|
|
|
|
|
2021-05-24 01:55:04 +08:00
|
|
|
// Map functions.
|
|
|
|
// --------------
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(coreMapRemove,
|
2021-06-03 04:44:01 +08:00
|
|
|
"map_remove(self:map, key:var) -> var\n"
|
|
|
|
"Remove the [key] from the map [self] and return it's value if the key "
|
2021-06-18 12:42:57 +08:00
|
|
|
"exists, otherwise it'll return null.") {
|
|
|
|
|
2021-05-24 01:55:04 +08:00
|
|
|
Map* map;
|
|
|
|
if (!validateArgMap(vm, 1, &map)) return;
|
|
|
|
Var key = ARG(2);
|
|
|
|
|
2021-06-03 04:44:01 +08:00
|
|
|
RET(mapRemoveKey(vm, map, key));
|
2021-05-24 01:55:04 +08:00
|
|
|
}
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
static void initializeBuiltinFN(PKVM* vm, Closure** bfn, const char* name,
|
|
|
|
int length, int arity, pkNativeFn ptr,
|
|
|
|
const char* docstring) {
|
|
|
|
Function* fn = newFunction(vm, name, length, NULL, true, docstring, NULL);
|
|
|
|
fn->arity = arity;
|
|
|
|
fn->native = ptr;
|
|
|
|
vmPushTempRef(vm, &fn->_super); // fn.
|
|
|
|
*bfn = newClosure(vm, fn);
|
|
|
|
vmPopTempRef(vm); // fn.
|
|
|
|
}
|
|
|
|
|
|
|
|
static void initializeBuiltinFunctions(PKVM* vm) {
|
2022-04-20 12:10:54 +08:00
|
|
|
#define INITIALIZE_BUILTIN_FN(name, fn, argc) \
|
|
|
|
initializeBuiltinFN(vm, &vm->builtins_funcs[vm->builtins_count++], name, \
|
2022-04-15 12:19:47 +08:00
|
|
|
(int)strlen(name), argc, fn, DOCSTRING(fn));
|
|
|
|
// General functions.
|
|
|
|
INITIALIZE_BUILTIN_FN("type_name", coreTypeName, 1);
|
|
|
|
INITIALIZE_BUILTIN_FN("help", coreHelp, -1);
|
|
|
|
INITIALIZE_BUILTIN_FN("assert", coreAssert, -1);
|
|
|
|
INITIALIZE_BUILTIN_FN("bin", coreBin, 1);
|
|
|
|
INITIALIZE_BUILTIN_FN("hex", coreHex, 1);
|
|
|
|
INITIALIZE_BUILTIN_FN("yield", coreYield, -1);
|
|
|
|
INITIALIZE_BUILTIN_FN("to_string", coreToString, 1);
|
2022-04-22 00:07:38 +08:00
|
|
|
INITIALIZE_BUILTIN_FN("chr", coreChr, 1);
|
|
|
|
INITIALIZE_BUILTIN_FN("ord", coreOrd, 1);
|
2022-04-15 12:19:47 +08:00
|
|
|
INITIALIZE_BUILTIN_FN("print", corePrint, -1);
|
|
|
|
INITIALIZE_BUILTIN_FN("input", coreInput, -1);
|
|
|
|
INITIALIZE_BUILTIN_FN("exit", coreExit, -1);
|
|
|
|
|
2022-04-22 00:07:38 +08:00
|
|
|
// FIXME:
|
|
|
|
// move this functions as methods. and make "append()" a builtin.
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
// String functions.
|
|
|
|
INITIALIZE_BUILTIN_FN("str_sub", coreStrSub, 3);
|
|
|
|
|
|
|
|
// List functions.
|
|
|
|
INITIALIZE_BUILTIN_FN("list_append", coreListAppend, 2);
|
|
|
|
INITIALIZE_BUILTIN_FN("list_join", coreListJoin, 1);
|
|
|
|
|
|
|
|
// Map functions.
|
|
|
|
INITIALIZE_BUILTIN_FN("map_remove", coreMapRemove, 2);
|
|
|
|
|
|
|
|
#undef INITIALIZE_BUILTIN_FN
|
|
|
|
}
|
|
|
|
|
2021-02-16 02:51:00 +08:00
|
|
|
/*****************************************************************************/
|
2021-05-23 04:59:32 +08:00
|
|
|
/* CORE MODULE METHODS */
|
2021-02-16 02:51:00 +08:00
|
|
|
/*****************************************************************************/
|
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
// Create a module and add it to the vm's core modules, returns the module.
|
|
|
|
static Module* newModuleInternal(PKVM* vm, const char* name) {
|
2021-05-23 04:59:32 +08:00
|
|
|
|
|
|
|
String* _name = newString(vm, name);
|
2022-04-11 16:04:22 +08:00
|
|
|
vmPushTempRef(vm, &_name->_super); // _name
|
2021-05-15 18:46:55 +08:00
|
|
|
|
2021-05-23 04:59:32 +08:00
|
|
|
// Check if any module with the same name already exists and assert to the
|
|
|
|
// hosting application.
|
2022-04-17 09:17:27 +08:00
|
|
|
if (vmGetModule(vm, _name) != NULL) {
|
2022-04-15 12:19:47 +08:00
|
|
|
ASSERT(false, stringFormat(vm,
|
|
|
|
"A module named '$' already exists", name)->data);
|
2021-02-16 02:51:00 +08:00
|
|
|
}
|
|
|
|
|
2022-04-17 09:17:27 +08:00
|
|
|
Module* module = newModule(vm);
|
|
|
|
module->name = _name;
|
|
|
|
module->initialized = true;
|
2021-05-23 04:59:32 +08:00
|
|
|
vmPopTempRef(vm); // _name
|
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
return module;
|
2021-05-11 14:38:23 +08:00
|
|
|
}
|
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
// An internal function to add a function to the given [module].
|
|
|
|
static void moduleAddFunctionInternal(PKVM* vm, Module* module,
|
2021-06-11 15:46:55 +08:00
|
|
|
const char* name, pkNativeFn fptr,
|
2021-06-18 12:42:57 +08:00
|
|
|
int arity, const char* docstring) {
|
2021-06-11 15:46:55 +08:00
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
Function* fn = newFunction(vm, name, (int)strlen(name),
|
2022-04-11 16:04:22 +08:00
|
|
|
module, true, docstring, NULL);
|
2021-05-23 04:59:32 +08:00
|
|
|
fn->native = fptr;
|
|
|
|
fn->arity = arity;
|
2022-04-12 04:49:09 +08:00
|
|
|
|
|
|
|
vmPushTempRef(vm, &fn->_super); // fn.
|
|
|
|
Closure* closure = newClosure(vm, fn);
|
|
|
|
moduleAddGlobal(vm, module, name, (uint32_t)strlen(name), VAR_OBJ(closure));
|
|
|
|
vmPopTempRef(vm); // fn.
|
2021-02-16 02:51:00 +08:00
|
|
|
}
|
2021-02-15 20:49:19 +08:00
|
|
|
|
2021-05-15 18:46:55 +08:00
|
|
|
// 'lang' library methods.
|
|
|
|
// -----------------------
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(stdLangClock,
|
|
|
|
"clock() -> num\n"
|
|
|
|
"Returns the number of seconds since the application started") {
|
|
|
|
|
2021-02-16 02:51:00 +08:00
|
|
|
RET(VAR_NUM((double)clock() / CLOCKS_PER_SEC));
|
|
|
|
}
|
2021-02-15 20:49:19 +08:00
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(stdLangGC,
|
|
|
|
"gc() -> num\n"
|
|
|
|
"Trigger garbage collection and return the amount of bytes cleaned.") {
|
|
|
|
|
2021-05-15 18:46:55 +08:00
|
|
|
size_t bytes_before = vm->bytes_allocated;
|
|
|
|
vmCollectGarbage(vm);
|
|
|
|
size_t garbage = bytes_before - vm->bytes_allocated;
|
|
|
|
RET(VAR_NUM((double)garbage));
|
|
|
|
}
|
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(stdLangDisas,
|
2022-04-12 04:49:09 +08:00
|
|
|
"disas(fn:Closure) -> String\n"
|
2021-06-18 12:42:57 +08:00
|
|
|
"Returns the disassembled opcode of the function [fn].") {
|
|
|
|
|
2022-04-03 19:29:03 +08:00
|
|
|
// TODO: support dissasemble class constructors and module main body.
|
|
|
|
|
2022-04-12 04:49:09 +08:00
|
|
|
Closure* closure;
|
|
|
|
if (!validateArgClosure(vm, 1, &closure)) return;
|
2021-06-12 14:56:09 +08:00
|
|
|
|
2022-04-12 04:49:09 +08:00
|
|
|
if (!validateCond(vm, !closure->fn->is_native,
|
2022-04-06 11:26:51 +08:00
|
|
|
"Cannot disassemble native functions.")) return;
|
|
|
|
|
2022-04-12 04:49:09 +08:00
|
|
|
dumpFunctionCode(vm, closure->fn);
|
2021-06-12 14:56:09 +08:00
|
|
|
}
|
|
|
|
|
2021-06-20 23:28:31 +08:00
|
|
|
#ifdef DEBUG
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(stdLangDebugBreak,
|
|
|
|
"debug_break() -> null\n"
|
|
|
|
"A debug function for development (will be removed).") {
|
|
|
|
|
2021-05-24 01:55:04 +08:00
|
|
|
DEBUG_BREAK();
|
|
|
|
}
|
2021-06-20 23:28:31 +08:00
|
|
|
#endif
|
2021-05-24 01:55:04 +08:00
|
|
|
|
2021-06-18 12:42:57 +08:00
|
|
|
DEF(stdLangWrite,
|
|
|
|
"write(...) -> null\n"
|
|
|
|
"Write function, just like print function but it wont put space between"
|
|
|
|
"args and write a new line at the end.") {
|
|
|
|
|
2021-06-15 15:37:49 +08:00
|
|
|
// If the host application doesn't provide any write function, discard the
|
2021-05-15 18:46:55 +08:00
|
|
|
// output.
|
2022-04-22 20:21:17 +08:00
|
|
|
if (vm->config.stdout_write == NULL) return;
|
2021-05-15 18:46:55 +08:00
|
|
|
|
|
|
|
String* str; //< Will be cleaned by garbage collector;
|
|
|
|
|
|
|
|
for (int i = 1; i <= ARGC; i++) {
|
|
|
|
Var arg = ARG(i);
|
|
|
|
// If it's already a string don't allocate a new string instead use it.
|
2021-06-04 09:28:42 +08:00
|
|
|
if (IS_OBJ_TYPE(arg, OBJ_STRING)) {
|
2021-05-15 18:46:55 +08:00
|
|
|
str = (String*)AS_OBJ(arg);
|
|
|
|
} else {
|
2021-05-24 06:17:52 +08:00
|
|
|
str = toString(vm, arg);
|
2021-05-15 18:46:55 +08:00
|
|
|
}
|
|
|
|
|
2022-04-22 20:21:17 +08:00
|
|
|
vm->config.stdout_write(vm, str->data);
|
2021-05-15 18:46:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
static void initializeCoreModules(PKVM* vm) {
|
2021-06-18 12:42:57 +08:00
|
|
|
#define MODULE_ADD_FN(module, name, fn, argc) \
|
|
|
|
moduleAddFunctionInternal(vm, module, name, fn, argc, DOCSTRING(fn))
|
2021-02-18 02:27:24 +08:00
|
|
|
|
2022-04-17 09:17:27 +08:00
|
|
|
#define NEW_MODULE(module, name_string) \
|
|
|
|
Module* module = newModuleInternal(vm, name_string); \
|
|
|
|
vmPushTempRef(vm, &module->_super); /* module */ \
|
|
|
|
vmRegisterModule(vm, module, module->name); \
|
|
|
|
vmPopTempRef(vm) /* module */
|
|
|
|
|
|
|
|
NEW_MODULE(lang, "lang");
|
2021-06-18 12:42:57 +08:00
|
|
|
MODULE_ADD_FN(lang, "clock", stdLangClock, 0);
|
|
|
|
MODULE_ADD_FN(lang, "gc", stdLangGC, 0);
|
|
|
|
MODULE_ADD_FN(lang, "disas", stdLangDisas, 1);
|
|
|
|
MODULE_ADD_FN(lang, "write", stdLangWrite, -1);
|
2021-05-24 06:17:52 +08:00
|
|
|
#ifdef DEBUG
|
2021-06-18 12:42:57 +08:00
|
|
|
MODULE_ADD_FN(lang, "debug_break", stdLangDebugBreak, 0);
|
2021-05-24 06:17:52 +08:00
|
|
|
#endif
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
#undef MODULE_ADD_FN
|
2022-04-17 09:17:27 +08:00
|
|
|
#undef NEW_MODULE
|
2021-02-12 01:35:43 +08:00
|
|
|
}
|
|
|
|
|
2022-04-17 09:17:27 +08:00
|
|
|
/*****************************************************************************/
|
2022-04-21 09:29:47 +08:00
|
|
|
/* BUILTIN CLASS CONSTRUCTORS */
|
2022-04-20 23:08:23 +08:00
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
static void _ctorNull(PKVM* vm) {
|
2022-04-21 11:06:37 +08:00
|
|
|
RET(VAR_NULL);
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _ctorBool(PKVM* vm) {
|
2022-04-21 11:06:37 +08:00
|
|
|
RET(toBool(ARG(1)));
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _ctorNumber(PKVM* vm) {
|
|
|
|
double value;
|
|
|
|
if (!validateNumeric(vm, ARG(1), &value, "Argument 1")) return;
|
2022-04-21 11:06:37 +08:00
|
|
|
RET(VAR_NUM(value));
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _ctorString(PKVM* vm) {
|
|
|
|
if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return;
|
|
|
|
if (ARGC == 0) {
|
2022-04-21 11:06:37 +08:00
|
|
|
RET(VAR_OBJ(newStringLength(vm, NULL, 0)));
|
2022-04-20 23:08:23 +08:00
|
|
|
return;
|
|
|
|
}
|
2022-04-21 11:06:37 +08:00
|
|
|
RET(VAR_OBJ(toString(vm, ARG(1))));
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _ctorList(PKVM* vm) {
|
|
|
|
List* list = newList(vm, ARGC);
|
|
|
|
vmPushTempRef(vm, &list->_super); // list.
|
|
|
|
for (int i = 0; i < ARGC; i++) {
|
|
|
|
listAppend(vm, list, ARG(i + 1));
|
|
|
|
}
|
|
|
|
vmPopTempRef(vm); // list.
|
2022-04-21 11:06:37 +08:00
|
|
|
RET(VAR_OBJ(list));
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _ctorMap(PKVM* vm) {
|
2022-04-21 11:06:37 +08:00
|
|
|
RET(VAR_OBJ(newMap(vm)));
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _ctorRange(PKVM* vm) {
|
|
|
|
double from, to;
|
|
|
|
if (!validateNumeric(vm, ARG(1), &from, "Argument 1")) return;
|
|
|
|
if (!validateNumeric(vm, ARG(2), &to, "Argument 2")) return;
|
|
|
|
|
2022-04-21 11:06:37 +08:00
|
|
|
RET(VAR_OBJ(newRange(vm, from, to)));
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _ctorFiber(PKVM* vm) {
|
|
|
|
Closure* closure;
|
|
|
|
if (!validateArgClosure(vm, 1, &closure)) return;
|
2022-04-21 11:06:37 +08:00
|
|
|
RET(VAR_OBJ(newFiber(vm, closure)));
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
2022-04-21 09:29:47 +08:00
|
|
|
/*****************************************************************************/
|
|
|
|
/* BUILTIN CLASS METHODS */
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
2022-04-21 11:06:37 +08:00
|
|
|
#define SELF (vm->fiber->self)
|
|
|
|
|
|
|
|
DEF(_listAppend,
|
|
|
|
"List.append(value:var) -> List\n"
|
|
|
|
"Append the [value] to the list and return the list.") {
|
|
|
|
|
|
|
|
ASSERT(IS_OBJ_TYPE(SELF, OBJ_LIST), OOPS);
|
|
|
|
|
|
|
|
listAppend(vm, ((List*)AS_OBJ(SELF)), ARG(1));
|
|
|
|
RET(SELF);
|
2022-04-21 09:29:47 +08:00
|
|
|
}
|
|
|
|
|
2022-04-21 11:06:37 +08:00
|
|
|
DEF(_fiberRun,
|
|
|
|
"Fiber.run(...) -> var\n"
|
|
|
|
"Runs the fiber's function with the provided arguments and returns it's "
|
|
|
|
"return value or the yielded value if it's yielded.") {
|
|
|
|
|
|
|
|
ASSERT(IS_OBJ_TYPE(SELF, OBJ_FIBER), OOPS);
|
|
|
|
Fiber* self = (Fiber*)AS_OBJ(SELF);
|
|
|
|
|
|
|
|
// Buffer of argument to call vmPrepareFiber().
|
|
|
|
Var* args[MAX_ARGC];
|
|
|
|
|
|
|
|
for (int i = 0; i < ARGC; i++) {
|
|
|
|
args[i] = &ARG(i + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Switch fiber and start execution.
|
|
|
|
if (vmPrepareFiber(vm, self, ARGC, args)) {
|
|
|
|
self->state = FIBER_RUNNING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEF(_fiberResume,
|
|
|
|
"Fiber.resume() -> var\n"
|
|
|
|
"Resumes a yielded function from a previous call of fiber_run() function. "
|
|
|
|
"Return it's return value or the yielded value if it's yielded.") {
|
|
|
|
|
|
|
|
ASSERT(IS_OBJ_TYPE(SELF, OBJ_FIBER), OOPS);
|
|
|
|
Fiber* self = (Fiber*)AS_OBJ(SELF);
|
|
|
|
|
|
|
|
if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return;
|
|
|
|
|
|
|
|
Var value = (ARGC == 1) ? ARG(1) : VAR_NULL;
|
|
|
|
|
|
|
|
// Switch fiber and resume execution.
|
|
|
|
if (vmSwitchFiber(vm, self, &value)) {
|
|
|
|
self->state = FIBER_RUNNING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef SELF
|
|
|
|
|
2022-04-20 23:08:23 +08:00
|
|
|
/*****************************************************************************/
|
|
|
|
/* BUILTIN CLASS INITIALIZATION */
|
2022-04-17 09:17:27 +08:00
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
static void initializePrimitiveClasses(PKVM* vm) {
|
2022-04-20 18:05:33 +08:00
|
|
|
for (int i = 0; i < PK_INSTANCE; i++) {
|
|
|
|
Class* super = NULL;
|
|
|
|
if (i != 0) super = vm->builtin_classes[PK_OBJECT];
|
|
|
|
const char* name = getPkVarTypeName((PkVarType)i);
|
2022-04-21 19:53:03 +08:00
|
|
|
Class* cls = newClass(vm, name, (int)strlen(name),
|
|
|
|
super, NULL, NULL, NULL);
|
|
|
|
vm->builtin_classes[i] = cls;
|
|
|
|
cls->class_of = (PkVarType)i;
|
2022-04-20 18:05:33 +08:00
|
|
|
}
|
|
|
|
|
2022-04-20 23:08:23 +08:00
|
|
|
#define ADD_CTOR(type, name, ptr, arity_) \
|
|
|
|
do { \
|
|
|
|
Function* fn = newFunction(vm, name, (int)strlen(name), \
|
|
|
|
NULL, true, NULL, NULL); \
|
|
|
|
fn->native = ptr; \
|
|
|
|
fn->arity = arity_; \
|
|
|
|
vmPushTempRef(vm, &fn->_super); /* fn. */ \
|
|
|
|
vm->builtin_classes[type]->ctor = newClosure(vm, fn); \
|
|
|
|
vmPopTempRef(vm); /* fn. */ \
|
|
|
|
} while (false)
|
|
|
|
|
|
|
|
ADD_CTOR(PK_NULL, "@ctorNull", _ctorNull, 0);
|
|
|
|
ADD_CTOR(PK_BOOL, "@ctorBool", _ctorBool, 1);
|
|
|
|
ADD_CTOR(PK_NUMBER, "@ctorNumber", _ctorNumber, 1);
|
|
|
|
ADD_CTOR(PK_STRING, "@ctorString", _ctorString, -1);
|
|
|
|
ADD_CTOR(PK_LIST, "@ctorList", _ctorList, -1);
|
|
|
|
ADD_CTOR(PK_MAP, "@ctorMap", _ctorMap, 0);
|
|
|
|
ADD_CTOR(PK_FIBER, "@ctorFiber", _ctorFiber, 1);
|
|
|
|
|
2022-04-21 09:29:47 +08:00
|
|
|
#undef ADD_CTOR
|
|
|
|
|
2022-04-21 11:06:37 +08:00
|
|
|
#define ADD_METHOD(type, name, ptr, arity_) \
|
|
|
|
do { \
|
|
|
|
Function* fn = newFunction(vm, name, (int)strlen(name), \
|
|
|
|
NULL, true, DOCSTRING(ptr), NULL); \
|
|
|
|
fn->native = ptr; \
|
|
|
|
fn->arity = arity_; \
|
|
|
|
vmPushTempRef(vm, &fn->_super); /* fn. */ \
|
|
|
|
pkClosureBufferWrite(&vm->builtin_classes[type]->methods, \
|
|
|
|
vm, newClosure(vm, fn)); \
|
|
|
|
vmPopTempRef(vm); /* fn. */ \
|
2022-04-21 09:29:47 +08:00
|
|
|
} while (false)
|
|
|
|
|
2022-04-21 11:06:37 +08:00
|
|
|
ADD_METHOD(PK_LIST, "append", _listAppend, 1);
|
|
|
|
ADD_METHOD(PK_FIBER, "run", _fiberRun, -1);
|
|
|
|
ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1);
|
2022-04-21 09:29:47 +08:00
|
|
|
|
|
|
|
#undef ADD_METHOD
|
2022-04-17 09:17:27 +08:00
|
|
|
}
|
|
|
|
|
2022-04-21 11:06:37 +08:00
|
|
|
#undef IS_NUM_BYTE
|
|
|
|
#undef DOCSTRING
|
|
|
|
#undef DEF
|
|
|
|
|
2021-02-16 02:51:00 +08:00
|
|
|
/*****************************************************************************/
|
|
|
|
/* OPERATORS */
|
|
|
|
/*****************************************************************************/
|
2021-02-11 01:23:48 +08:00
|
|
|
|
2022-04-20 23:08:23 +08:00
|
|
|
Var preConstructSelf(PKVM* vm, Class* cls) {
|
|
|
|
|
|
|
|
#define NO_INSTANCE(type_name) \
|
|
|
|
VM_SET_ERROR(vm, newString(vm, \
|
|
|
|
"Class '" type_name "' cannot be instanciated."))
|
|
|
|
|
2022-04-21 19:53:03 +08:00
|
|
|
switch (cls->class_of) {
|
|
|
|
case PK_OBJECT:
|
|
|
|
NO_INSTANCE("Object");
|
|
|
|
return VAR_NULL;
|
|
|
|
|
|
|
|
case PK_NULL:
|
|
|
|
case PK_BOOL:
|
|
|
|
case PK_NUMBER:
|
|
|
|
case PK_STRING:
|
|
|
|
case PK_LIST:
|
|
|
|
case PK_MAP:
|
|
|
|
case PK_RANGE:
|
|
|
|
return VAR_NULL; // Constructor will override the null.
|
|
|
|
|
|
|
|
case PK_MODULE:
|
|
|
|
NO_INSTANCE("Module");
|
|
|
|
return VAR_NULL;
|
|
|
|
|
|
|
|
case PK_CLOSURE:
|
|
|
|
NO_INSTANCE("Closure");
|
|
|
|
return VAR_NULL;
|
|
|
|
|
|
|
|
case PK_FIBER:
|
|
|
|
return VAR_NULL;
|
|
|
|
|
|
|
|
case PK_CLASS:
|
|
|
|
NO_INSTANCE("Class");
|
|
|
|
return VAR_NULL;
|
|
|
|
|
|
|
|
case PK_INSTANCE:
|
|
|
|
return VAR_OBJ(newInstance(vm, cls));
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
2022-04-21 19:53:03 +08:00
|
|
|
UNREACHABLE();
|
|
|
|
return VAR_NULL;
|
2022-04-20 23:08:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Class* getClass(PKVM* vm, Var instance) {
|
|
|
|
PkVarType type = getVarType(instance);
|
|
|
|
if (0 <= type && type < PK_INSTANCE) {
|
|
|
|
return vm->builtin_classes[type];
|
|
|
|
}
|
|
|
|
ASSERT(IS_OBJ_TYPE(instance, OBJ_INST), OOPS);
|
|
|
|
Instance* inst = (Instance*)AS_OBJ(instance);
|
|
|
|
return inst->cls;
|
|
|
|
}
|
|
|
|
|
|
|
|
Var getMethod(PKVM* vm, Var self, String* name, bool* is_method) {
|
|
|
|
|
|
|
|
Class* cls = getClass(vm, self);
|
|
|
|
ASSERT(cls != NULL, OOPS);
|
|
|
|
|
|
|
|
Class* cls_ = cls;
|
|
|
|
do {
|
|
|
|
for (int i = 0; i < (int)cls_->methods.count; i++) {
|
|
|
|
Closure* method = cls_->methods.data[i];
|
|
|
|
if (IS_CSTR_EQ(name, method->fn->name, name->length)) {
|
|
|
|
if (is_method) *is_method = true;
|
|
|
|
return VAR_OBJ(method);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cls_ = cls_->super_class;
|
|
|
|
} while (cls_ != NULL);
|
|
|
|
|
|
|
|
// If the attribute not found it'll set an error.
|
|
|
|
if (is_method) *is_method = false;
|
|
|
|
return varGetAttrib(vm, self, name);
|
|
|
|
}
|
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
#define UNSUPPORTED_OPERAND_TYPES(op) \
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Unsupported operand types for " \
|
|
|
|
"operator '" op "' $ and $", varTypeName(v1), varTypeName(v2)))
|
2021-05-12 15:57:51 +08:00
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
#define RIGHT_OPERAND "Right operand"
|
2021-02-12 01:35:43 +08:00
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
Var varAdd(PKVM* vm, Var v1, Var v2) {
|
2021-02-12 01:35:43 +08:00
|
|
|
double d1, d2;
|
2021-06-13 21:50:39 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
if (isNumeric(v1, &d1)) {
|
2021-06-13 21:50:39 +08:00
|
|
|
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
2021-02-12 01:35:43 +08:00
|
|
|
return VAR_NUM(d1 + d2);
|
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-04-26 17:34:30 +08:00
|
|
|
if (IS_OBJ(v1) && IS_OBJ(v2)) {
|
|
|
|
Object *o1 = AS_OBJ(v1), *o2 = AS_OBJ(v2);
|
|
|
|
switch (o1->type) {
|
|
|
|
|
|
|
|
case OBJ_STRING:
|
|
|
|
{
|
|
|
|
if (o2->type == OBJ_STRING) {
|
2021-05-24 06:17:52 +08:00
|
|
|
return VAR_OBJ(stringJoin(vm, (String*)o1, (String*)o2));
|
2021-04-26 17:34:30 +08:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case OBJ_LIST:
|
2021-05-31 08:01:41 +08:00
|
|
|
{
|
|
|
|
if (o2->type == OBJ_LIST) {
|
2021-06-16 11:36:25 +08:00
|
|
|
return VAR_OBJ(listJoin(vm, (List*)o1, (List*)o2));
|
2021-05-31 08:01:41 +08:00
|
|
|
}
|
2021-06-11 15:46:55 +08:00
|
|
|
} break;
|
2021-04-26 17:34:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
UNSUPPORTED_OPERAND_TYPES("+");
|
2021-02-12 01:35:43 +08:00
|
|
|
return VAR_NULL;
|
2021-02-11 01:23:48 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
Var varSubtract(PKVM* vm, Var v1, Var v2) {
|
2021-02-15 20:49:19 +08:00
|
|
|
double d1, d2;
|
2021-06-13 21:50:39 +08:00
|
|
|
|
2021-02-15 20:49:19 +08:00
|
|
|
if (isNumeric(v1, &d1)) {
|
2021-06-13 21:50:39 +08:00
|
|
|
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
2021-02-15 20:49:19 +08:00
|
|
|
return VAR_NUM(d1 - d2);
|
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
UNSUPPORTED_OPERAND_TYPES("-");
|
2021-02-12 01:35:43 +08:00
|
|
|
return VAR_NULL;
|
2021-02-11 01:23:48 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
Var varMultiply(PKVM* vm, Var v1, Var v2) {
|
2021-02-12 01:35:43 +08:00
|
|
|
double d1, d2;
|
2021-06-13 21:50:39 +08:00
|
|
|
|
2021-02-12 01:35:43 +08:00
|
|
|
if (isNumeric(v1, &d1)) {
|
2021-06-13 21:50:39 +08:00
|
|
|
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
2021-02-12 01:35:43 +08:00
|
|
|
return VAR_NUM(d1 * d2);
|
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
2021-02-11 01:23:48 +08:00
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
UNSUPPORTED_OPERAND_TYPES("*");
|
2021-02-12 01:35:43 +08:00
|
|
|
return VAR_NULL;
|
2021-02-11 01:23:48 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
Var varDivide(PKVM* vm, Var v1, Var v2) {
|
2021-02-16 02:51:00 +08:00
|
|
|
double d1, d2;
|
2021-06-13 21:50:39 +08:00
|
|
|
|
2021-02-16 02:51:00 +08:00
|
|
|
if (isNumeric(v1, &d1)) {
|
2021-06-13 21:50:39 +08:00
|
|
|
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
2021-02-16 02:51:00 +08:00
|
|
|
return VAR_NUM(d1 / d2);
|
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
UNSUPPORTED_OPERAND_TYPES("/");
|
2021-05-12 15:57:51 +08:00
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
Var varModulo(PKVM* vm, Var v1, Var v2) {
|
|
|
|
double d1, d2;
|
2021-06-13 21:50:39 +08:00
|
|
|
|
2021-05-12 15:57:51 +08:00
|
|
|
if (isNumeric(v1, &d1)) {
|
2021-06-13 21:50:39 +08:00
|
|
|
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
2021-05-12 15:57:51 +08:00
|
|
|
return VAR_NUM(fmod(d1, d2));
|
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-06-04 09:28:42 +08:00
|
|
|
if (IS_OBJ_TYPE(v1, OBJ_STRING)) {
|
|
|
|
//const String* str = (const String*)AS_OBJ(v1);
|
2021-05-12 15:57:51 +08:00
|
|
|
TODO; // "fmt" % v2.
|
|
|
|
}
|
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
UNSUPPORTED_OPERAND_TYPES("%");
|
2021-02-12 01:35:43 +08:00
|
|
|
return VAR_NULL;
|
2021-02-12 14:53:52 +08:00
|
|
|
}
|
|
|
|
|
2021-06-11 15:46:55 +08:00
|
|
|
Var varBitAnd(PKVM* vm, Var v1, Var v2) {
|
|
|
|
int64_t i1, i2;
|
2021-06-13 21:50:39 +08:00
|
|
|
|
2021-06-11 15:46:55 +08:00
|
|
|
if (isInteger(v1, &i1)) {
|
2021-06-13 21:50:39 +08:00
|
|
|
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
2021-06-12 14:56:09 +08:00
|
|
|
return VAR_NUM((double)(i1 & i2));
|
2021-06-11 15:46:55 +08:00
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
UNSUPPORTED_OPERAND_TYPES("&");
|
2021-06-11 15:46:55 +08:00
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-06-13 12:13:05 +08:00
|
|
|
Var varBitOr(PKVM* vm, Var v1, Var v2) {
|
|
|
|
int64_t i1, i2;
|
2021-06-13 21:50:39 +08:00
|
|
|
|
2021-06-13 12:13:05 +08:00
|
|
|
if (isInteger(v1, &i1)) {
|
2021-06-13 21:50:39 +08:00
|
|
|
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
2021-06-13 12:13:05 +08:00
|
|
|
return VAR_NUM((double)(i1 | i2));
|
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
UNSUPPORTED_OPERAND_TYPES("|");
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
Var varBitXor(PKVM* vm, Var v1, Var v2) {
|
|
|
|
int64_t i1, i2;
|
|
|
|
|
|
|
|
if (isInteger(v1, &i1)) {
|
|
|
|
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
|
|
|
return VAR_NUM((double)(i1 ^ i2));
|
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
UNSUPPORTED_OPERAND_TYPES("^");
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
Var varBitLshift(PKVM* vm, Var v1, Var v2) {
|
|
|
|
int64_t i1, i2;
|
|
|
|
|
|
|
|
if (isInteger(v1, &i1)) {
|
|
|
|
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
|
|
|
return VAR_NUM((double)(i1 << i2));
|
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
UNSUPPORTED_OPERAND_TYPES("<<");
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
Var varBitRshift(PKVM* vm, Var v1, Var v2) {
|
|
|
|
int64_t i1, i2;
|
|
|
|
|
|
|
|
if (isInteger(v1, &i1)) {
|
|
|
|
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
|
|
|
return VAR_NUM((double)(i1 >> i2));
|
|
|
|
}
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
UNSUPPORTED_OPERAND_TYPES(">>");
|
2021-06-13 12:13:05 +08:00
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-06-16 23:54:02 +08:00
|
|
|
Var varBitNot(PKVM* vm, Var v) {
|
|
|
|
int64_t i;
|
2021-06-17 03:54:07 +08:00
|
|
|
if (!validateInteger(vm, v, &i, "Unary operand")) return VAR_NULL;
|
|
|
|
return VAR_NUM((double)(~i));
|
2021-06-16 23:54:02 +08:00
|
|
|
}
|
|
|
|
|
2021-05-24 01:55:04 +08:00
|
|
|
bool varGreater(Var v1, Var v2) {
|
2021-02-15 20:49:19 +08:00
|
|
|
double d1, d2;
|
2021-06-13 21:50:39 +08:00
|
|
|
|
2021-02-15 20:49:19 +08:00
|
|
|
if (isNumeric(v1, &d1) && isNumeric(v2, &d2)) {
|
|
|
|
return d1 > d2;
|
|
|
|
}
|
|
|
|
|
|
|
|
TODO;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-05-24 01:55:04 +08:00
|
|
|
bool varLesser(Var v1, Var v2) {
|
2021-02-15 20:49:19 +08:00
|
|
|
double d1, d2;
|
2021-06-13 21:50:39 +08:00
|
|
|
|
2021-02-15 20:49:19 +08:00
|
|
|
if (isNumeric(v1, &d1) && isNumeric(v2, &d2)) {
|
|
|
|
return d1 < d2;
|
|
|
|
}
|
|
|
|
|
|
|
|
TODO;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-13 21:50:39 +08:00
|
|
|
#undef RIGHT_OPERAND
|
|
|
|
#undef UNSUPPORTED_OPERAND_TYPES
|
|
|
|
|
2021-06-23 18:21:59 +08:00
|
|
|
bool varContains(PKVM* vm, Var elem, Var container) {
|
|
|
|
if (!IS_OBJ(container)) {
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "'$' is not iterable.",
|
|
|
|
varTypeName(container)));
|
|
|
|
}
|
|
|
|
Object* obj = AS_OBJ(container);
|
|
|
|
|
|
|
|
switch (obj->type) {
|
|
|
|
case OBJ_STRING: {
|
|
|
|
if (!IS_OBJ_TYPE(elem, OBJ_STRING)) {
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Expected a string operand."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
String* sub = (String*)AS_OBJ(elem);
|
|
|
|
String* str = (String*)AS_OBJ(container);
|
|
|
|
if (sub->length > str->length) return false;
|
|
|
|
|
|
|
|
TODO;
|
|
|
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case OBJ_LIST: {
|
|
|
|
List* list = (List*)AS_OBJ(container);
|
|
|
|
for (uint32_t i = 0; i < list->elements.count; i++) {
|
|
|
|
if (isValuesEqual(elem, list->elements.data[i])) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
} break;
|
|
|
|
|
2021-06-23 23:33:26 +08:00
|
|
|
case OBJ_MAP: {
|
|
|
|
Map* map = (Map*)AS_OBJ(container);
|
|
|
|
return !IS_UNDEF(mapGet(map, elem));
|
|
|
|
} break;
|
2021-06-23 18:21:59 +08:00
|
|
|
}
|
2022-04-15 12:19:47 +08:00
|
|
|
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Argument of type $ is not iterable.",
|
|
|
|
varTypeName(container)));
|
|
|
|
return VAR_NULL;
|
2021-06-23 18:21:59 +08:00
|
|
|
}
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
2021-02-18 02:27:24 +08:00
|
|
|
|
2022-04-20 12:10:54 +08:00
|
|
|
#define ERR_NO_ATTRIB(vm, on, attrib) \
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "'$' object has no attribute named '$'.", \
|
2021-06-11 15:46:55 +08:00
|
|
|
varTypeName(on), attrib->data))
|
2021-02-18 02:27:24 +08:00
|
|
|
|
2021-02-16 02:51:00 +08:00
|
|
|
if (!IS_OBJ(on)) {
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not subscriptable.",
|
|
|
|
varTypeName(on)));
|
2021-02-16 02:51:00 +08:00
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
2021-05-07 17:41:19 +08:00
|
|
|
Object* obj = AS_OBJ(on);
|
2021-02-16 02:51:00 +08:00
|
|
|
switch (obj->type) {
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_STRING: {
|
2021-06-10 07:35:14 +08:00
|
|
|
String* str = (String*)obj;
|
2021-06-30 14:39:17 +08:00
|
|
|
switch (attrib->hash) {
|
2021-06-10 07:35:14 +08:00
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("length", 0x83d03615):
|
2021-06-10 07:35:14 +08:00
|
|
|
return VAR_NUM((double)(str->length));
|
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("lower", 0xb51d04ba):
|
2021-06-10 07:35:14 +08:00
|
|
|
return VAR_OBJ(stringLower(vm, str));
|
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("upper", 0xa8c6a47):
|
2021-06-10 07:35:14 +08:00
|
|
|
return VAR_OBJ(stringUpper(vm, str));
|
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("strip", 0xfd1b18d1):
|
2021-06-10 07:35:14 +08:00
|
|
|
return VAR_OBJ(stringStrip(vm, str));
|
2021-02-18 02:27:24 +08:00
|
|
|
}
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-05-05 13:55:34 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_LIST: {
|
2021-06-10 07:35:14 +08:00
|
|
|
List* list = (List*)obj;
|
2021-06-30 14:39:17 +08:00
|
|
|
switch (attrib->hash) {
|
2021-06-10 07:35:14 +08:00
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("length", 0x83d03615):
|
2021-06-10 07:35:14 +08:00
|
|
|
return VAR_NUM((double)(list->elements.count));
|
2021-02-18 02:27:24 +08:00
|
|
|
}
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-05-05 13:55:34 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_MAP: {
|
2021-06-10 07:35:14 +08:00
|
|
|
// map = { "foo" : 42, "can't access" : 32 }
|
2022-04-06 11:26:51 +08:00
|
|
|
// val = map.foo ## <-- This should be error
|
|
|
|
// Only the map's attributes are accessed here.
|
2021-06-10 07:35:14 +08:00
|
|
|
TODO;
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-05-06 13:00:02 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_RANGE: {
|
2021-05-24 06:17:52 +08:00
|
|
|
Range* range = (Range*)obj;
|
2021-06-30 14:39:17 +08:00
|
|
|
switch (attrib->hash) {
|
2021-05-24 06:17:52 +08:00
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("as_list", 0x1562c22):
|
2021-06-10 07:35:14 +08:00
|
|
|
return VAR_OBJ(rangeAsList(vm, range));
|
|
|
|
|
2021-06-14 02:41:13 +08:00
|
|
|
// We can't use 'start', 'end' since 'end' in pocketlang is a
|
|
|
|
// keyword. Also we can't use 'from', 'to' since 'from' is a keyword
|
|
|
|
// too. So, we're using 'first' and 'last' to access the range limits.
|
2021-06-14 00:28:31 +08:00
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("first", 0x4881d841):
|
2021-06-14 00:38:00 +08:00
|
|
|
return VAR_NUM(range->from);
|
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("last", 0x63e1d819):
|
2021-06-14 00:38:00 +08:00
|
|
|
return VAR_NUM(range->to);
|
2021-05-24 06:17:52 +08:00
|
|
|
}
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-05-24 06:17:52 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_MODULE: {
|
2022-04-11 16:04:22 +08:00
|
|
|
Module* module = (Module*)obj;
|
2021-02-16 02:51:00 +08:00
|
|
|
|
2021-05-05 13:55:34 +08:00
|
|
|
// Search in globals.
|
2022-04-11 16:04:22 +08:00
|
|
|
int index = moduleGetGlobalIndex(module, attrib->data, attrib->length);
|
2021-05-05 13:55:34 +08:00
|
|
|
if (index != -1) {
|
2022-04-11 16:04:22 +08:00
|
|
|
ASSERT_INDEX((uint32_t)index, module->globals.count);
|
|
|
|
return module->globals.data[index];
|
2021-05-05 13:55:34 +08:00
|
|
|
}
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-02-16 02:51:00 +08:00
|
|
|
|
|
|
|
case OBJ_FUNC:
|
2022-04-15 12:19:47 +08:00
|
|
|
break;
|
2022-04-12 04:49:09 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_CLOSURE: {
|
2022-04-12 04:49:09 +08:00
|
|
|
Closure* closure = (Closure*)obj;
|
2021-06-30 14:39:17 +08:00
|
|
|
switch (attrib->hash) {
|
2021-06-10 07:35:14 +08:00
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("arity", 0x3e96bd7a):
|
2022-04-12 04:49:09 +08:00
|
|
|
return VAR_NUM((double)(closure->fn->arity));
|
2021-06-10 07:35:14 +08:00
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
case CHECK_HASH("name", 0x8d39bde6):
|
2022-04-12 04:49:09 +08:00
|
|
|
return VAR_OBJ(newString(vm, closure->fn->name));
|
2021-06-10 07:35:14 +08:00
|
|
|
}
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-06-10 07:35:14 +08:00
|
|
|
|
2022-04-09 02:53:12 +08:00
|
|
|
case OBJ_UPVALUE:
|
2022-04-15 12:19:47 +08:00
|
|
|
UNREACHABLE(); // Upvalues aren't first class objects.
|
|
|
|
break;
|
2022-04-09 02:53:12 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_FIBER: {
|
|
|
|
Fiber* fb = (Fiber*)obj;
|
|
|
|
switch (attrib->hash) {
|
2021-06-23 12:18:15 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case CHECK_HASH("is_done", 0x789c2706):
|
|
|
|
return VAR_BOOL(fb->state == FIBER_DONE);
|
2021-06-23 12:18:15 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case CHECK_HASH("function", 0x9ed64249):
|
|
|
|
return VAR_OBJ(fb->closure);
|
2021-06-23 12:18:15 +08:00
|
|
|
}
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-06-20 23:28:31 +08:00
|
|
|
|
|
|
|
case OBJ_CLASS:
|
|
|
|
TODO;
|
2022-04-15 12:19:47 +08:00
|
|
|
break;
|
2021-06-20 23:28:31 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_INST: {
|
2021-06-30 14:39:17 +08:00
|
|
|
Var value;
|
2022-04-15 12:19:47 +08:00
|
|
|
if (instGetAttrib(vm, (Instance*)obj, attrib, &value)) {
|
|
|
|
return value;
|
2021-06-20 23:28:31 +08:00
|
|
|
}
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-02-16 02:51:00 +08:00
|
|
|
}
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
|
|
|
return VAR_NULL;
|
|
|
|
|
|
|
|
#undef ERR_NO_ATTRIB
|
2021-02-16 02:51:00 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value) {
|
2021-02-18 02:27:24 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
// Set error for accessing non-existed attribute.
|
|
|
|
#define ERR_NO_ATTRIB(vm, on, attrib) \
|
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, \
|
|
|
|
"'$' object has no mutable attribute named '$'", \
|
|
|
|
varTypeName(on), attrib->data))
|
|
|
|
|
2021-06-10 07:35:14 +08:00
|
|
|
#define ATTRIB_IMMUTABLE(name) \
|
2021-05-06 22:19:30 +08:00
|
|
|
do { \
|
2021-06-10 07:35:14 +08:00
|
|
|
if ((attrib->length == strlen(name) && strcmp(name, attrib->data) == 0)) { \
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "'$' attribute is immutable.", name)); \
|
2021-05-06 22:19:30 +08:00
|
|
|
return; \
|
|
|
|
} \
|
2021-02-18 02:27:24 +08:00
|
|
|
} while (false)
|
|
|
|
|
|
|
|
if (!IS_OBJ(on)) {
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not subscriptable.",
|
|
|
|
varTypeName(on)));
|
2021-02-18 02:27:24 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-07 17:41:19 +08:00
|
|
|
Object* obj = AS_OBJ(on);
|
2021-02-18 02:27:24 +08:00
|
|
|
switch (obj->type) {
|
|
|
|
case OBJ_STRING:
|
|
|
|
ATTRIB_IMMUTABLE("length");
|
2021-06-10 07:35:14 +08:00
|
|
|
ATTRIB_IMMUTABLE("lower");
|
|
|
|
ATTRIB_IMMUTABLE("upper");
|
|
|
|
ATTRIB_IMMUTABLE("strip");
|
2021-06-11 15:46:55 +08:00
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
2021-02-18 02:27:24 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
case OBJ_LIST:
|
|
|
|
ATTRIB_IMMUTABLE("length");
|
2021-06-11 15:46:55 +08:00
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
2021-02-18 02:27:24 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
case OBJ_MAP:
|
|
|
|
TODO;
|
2021-06-11 15:46:55 +08:00
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
2021-02-18 02:27:24 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
case OBJ_RANGE:
|
2021-06-10 07:35:14 +08:00
|
|
|
ATTRIB_IMMUTABLE("as_list");
|
2021-06-14 00:38:00 +08:00
|
|
|
ATTRIB_IMMUTABLE("first");
|
|
|
|
ATTRIB_IMMUTABLE("last");
|
2021-06-11 15:46:55 +08:00
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
2021-02-18 02:27:24 +08:00
|
|
|
return;
|
|
|
|
|
2022-04-11 16:04:22 +08:00
|
|
|
case OBJ_MODULE: {
|
|
|
|
Module* module = (Module*)obj;
|
|
|
|
int index = moduleGetGlobalIndex(module, attrib->data, attrib->length);
|
2021-05-05 13:55:34 +08:00
|
|
|
if (index != -1) {
|
2022-04-11 16:04:22 +08:00
|
|
|
ASSERT_INDEX((uint32_t)index, module->globals.count);
|
|
|
|
module->globals.data[index] = value;
|
2021-05-05 13:55:34 +08:00
|
|
|
return;
|
|
|
|
}
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-02-18 02:27:24 +08:00
|
|
|
|
|
|
|
case OBJ_FUNC:
|
2022-04-15 12:19:47 +08:00
|
|
|
UNREACHABLE(); // Functions aren't first class objects.
|
2022-04-12 04:49:09 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
case OBJ_CLOSURE:
|
2021-06-10 07:35:14 +08:00
|
|
|
ATTRIB_IMMUTABLE("arity");
|
|
|
|
ATTRIB_IMMUTABLE("name");
|
2021-06-11 15:46:55 +08:00
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
2021-02-18 02:27:24 +08:00
|
|
|
return;
|
|
|
|
|
2022-04-09 02:53:12 +08:00
|
|
|
case OBJ_UPVALUE:
|
2022-04-15 12:19:47 +08:00
|
|
|
UNREACHABLE(); // Upvalues aren't first class objects.
|
2022-04-09 02:53:12 +08:00
|
|
|
return;
|
|
|
|
|
2021-02-25 17:03:06 +08:00
|
|
|
case OBJ_FIBER:
|
2021-06-11 15:46:55 +08:00
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
2021-02-25 17:03:06 +08:00
|
|
|
return;
|
|
|
|
|
2021-06-20 23:28:31 +08:00
|
|
|
case OBJ_CLASS:
|
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
2021-02-18 02:27:24 +08:00
|
|
|
return;
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_INST: {
|
2021-06-30 14:39:17 +08:00
|
|
|
if (!instSetAttrib(vm, (Instance*)obj, attrib, value)) {
|
|
|
|
// If we has error by now, that means the set value type is
|
|
|
|
// incompatible. No need for us to set an other error, just return.
|
|
|
|
if (VM_HAS_ERROR(vm)) return;
|
2021-06-20 23:28:31 +08:00
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
|
|
|
}
|
|
|
|
|
2021-06-30 14:39:17 +08:00
|
|
|
// If we reached here, that means the attribute exists and we have
|
|
|
|
// updated the value.
|
|
|
|
return;
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-02-18 02:27:24 +08:00
|
|
|
}
|
2021-06-10 07:35:14 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
ERR_NO_ATTRIB(vm, on, attrib);
|
|
|
|
return;
|
2021-02-16 02:51:00 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
#undef ATTRIB_IMMUTABLE
|
2021-06-12 14:56:09 +08:00
|
|
|
#undef ERR_NO_ATTRIB
|
2022-04-15 12:19:47 +08:00
|
|
|
}
|
2021-06-12 14:56:09 +08:00
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
Var varGetSubscript(PKVM* vm, Var on, Var key) {
|
2021-02-16 02:51:00 +08:00
|
|
|
if (!IS_OBJ(on)) {
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not subscriptable.",
|
|
|
|
varTypeName(on)));
|
2021-02-16 02:51:00 +08:00
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
Object* obj = AS_OBJ(on);
|
|
|
|
switch (obj->type) {
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_STRING: {
|
2021-06-10 07:35:14 +08:00
|
|
|
int64_t index;
|
2021-02-18 02:27:24 +08:00
|
|
|
String* str = ((String*)obj);
|
2021-06-02 17:33:29 +08:00
|
|
|
if (!validateInteger(vm, key, &index, "List index")) {
|
2021-02-18 02:27:24 +08:00
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
if (!validateIndex(vm, index, str->length, "String")) {
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
2021-05-06 22:19:30 +08:00
|
|
|
String* c = newStringLength(vm, str->data + index, 1);
|
2021-02-18 02:27:24 +08:00
|
|
|
return VAR_OBJ(c);
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-02-16 02:51:00 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_LIST: {
|
2021-06-10 07:35:14 +08:00
|
|
|
int64_t index;
|
2021-06-09 18:42:26 +08:00
|
|
|
pkVarBuffer* elems = &((List*)obj)->elements;
|
2021-06-02 17:33:29 +08:00
|
|
|
if (!validateInteger(vm, key, &index, "List index")) {
|
2021-02-16 02:51:00 +08:00
|
|
|
return VAR_NULL;
|
|
|
|
}
|
2021-06-10 07:35:14 +08:00
|
|
|
if (!validateIndex(vm, index, elems->count, "List")) {
|
2021-02-16 02:51:00 +08:00
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
return elems->data[index];
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-02-16 02:51:00 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_MAP: {
|
2021-05-06 13:00:02 +08:00
|
|
|
Var value = mapGet((Map*)obj, key);
|
|
|
|
if (IS_UNDEF(value)) {
|
2021-05-06 22:19:30 +08:00
|
|
|
|
2021-05-24 06:17:52 +08:00
|
|
|
String* key_str = toString(vm, key);
|
2021-05-06 13:00:02 +08:00
|
|
|
vmPushTempRef(vm, &key_str->_super);
|
2021-05-16 15:05:54 +08:00
|
|
|
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Invalid key '@'.", key_str));
|
2021-05-16 15:05:54 +08:00
|
|
|
} else {
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "Key '@' not exists", key_str));
|
2021-05-16 15:05:54 +08:00
|
|
|
}
|
2021-05-06 13:00:02 +08:00
|
|
|
vmPopTempRef(vm);
|
|
|
|
return VAR_NULL;
|
|
|
|
}
|
|
|
|
return value;
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-05-06 13:00:02 +08:00
|
|
|
|
2021-02-16 02:51:00 +08:00
|
|
|
case OBJ_FUNC:
|
2022-04-09 02:53:12 +08:00
|
|
|
case OBJ_UPVALUE:
|
2022-04-15 12:19:47 +08:00
|
|
|
UNREACHABLE(); // Not first class objects.
|
2021-02-16 02:51:00 +08:00
|
|
|
}
|
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not subscriptable.",
|
|
|
|
varTypeName(on)));
|
|
|
|
return VAR_NULL;
|
2021-02-16 02:51:00 +08:00
|
|
|
}
|
|
|
|
|
2021-05-09 18:28:00 +08:00
|
|
|
void varsetSubscript(PKVM* vm, Var on, Var key, Var value) {
|
2021-02-16 02:51:00 +08:00
|
|
|
if (!IS_OBJ(on)) {
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not subscriptable.",
|
|
|
|
varTypeName(on)));
|
2021-02-16 02:51:00 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Object* obj = AS_OBJ(on);
|
|
|
|
switch (obj->type) {
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_LIST: {
|
2021-06-10 07:35:14 +08:00
|
|
|
int64_t index;
|
2021-06-09 18:42:26 +08:00
|
|
|
pkVarBuffer* elems = &((List*)obj)->elements;
|
2021-06-02 17:33:29 +08:00
|
|
|
if (!validateInteger(vm, key, &index, "List index")) return;
|
2021-06-10 07:35:14 +08:00
|
|
|
if (!validateIndex(vm, index, elems->count, "List")) return;
|
2021-02-16 02:51:00 +08:00
|
|
|
elems->data[index] = value;
|
|
|
|
return;
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-02-16 02:51:00 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
case OBJ_MAP: {
|
2021-05-06 13:00:02 +08:00
|
|
|
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
|
2021-06-11 15:46:55 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not hashable.",
|
|
|
|
varTypeName(key)));
|
2021-05-06 13:00:02 +08:00
|
|
|
} else {
|
2021-05-19 02:59:09 +08:00
|
|
|
mapSet(vm, (Map*)obj, key, value);
|
2021-05-06 13:00:02 +08:00
|
|
|
}
|
|
|
|
return;
|
2022-04-15 12:19:47 +08:00
|
|
|
} break;
|
2021-05-06 13:00:02 +08:00
|
|
|
|
2021-02-16 02:51:00 +08:00
|
|
|
case OBJ_FUNC:
|
2022-04-09 02:53:12 +08:00
|
|
|
case OBJ_UPVALUE:
|
2021-02-16 02:51:00 +08:00
|
|
|
UNREACHABLE();
|
|
|
|
}
|
2021-06-12 14:56:09 +08:00
|
|
|
|
2022-04-15 12:19:47 +08:00
|
|
|
VM_SET_ERROR(vm, stringFormat(vm, "$ type is not subscriptable.",
|
|
|
|
varTypeName(on)));
|
|
|
|
return;
|
2021-02-16 02:51:00 +08:00
|
|
|
}
|