mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-05 20:26:53 +08:00
native api function improvements (#153)
This commit is contained in:
parent
b35b7a48cc
commit
6ad5dfe9f7
@ -19,7 +19,7 @@ void initObj(Obj* obj, ObjType type) {
|
||||
obj->type = type;
|
||||
}
|
||||
|
||||
bool objGetAttrib(PKVM* vm, void* instance, PkStringPtr attrib) {
|
||||
void objGetAttrib(PKVM* vm, void* instance, PkStringPtr attrib) {
|
||||
|
||||
Obj* obj = (Obj*)instance;
|
||||
// TODO: assert obj type is valid.
|
||||
@ -28,11 +28,24 @@ bool objGetAttrib(PKVM* vm, void* instance, PkStringPtr attrib) {
|
||||
File* file = (File*)obj;
|
||||
if (strcmp(attrib.string, "closed") == 0) {
|
||||
pkReturnBool(vm, file->closed);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false; // Attribute not found.
|
||||
return; // Attribute not found.
|
||||
}
|
||||
|
||||
bool objSetAttrib(PKVM* vm, void* instance, PkStringPtr attrib) {
|
||||
Obj* obj = (Obj*)instance;
|
||||
// TODO: assert obj type is valid.
|
||||
|
||||
if (obj->type == OBJ_FILE) {
|
||||
File* file = (File*)obj;
|
||||
// Nothing to change.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void freeObj(PKVM* vm, void* instance) {
|
||||
|
@ -59,7 +59,7 @@ void initObj(Obj* obj, ObjType type);
|
||||
|
||||
// A function callback called by pocket VM to get attribute of a native
|
||||
// instance.
|
||||
bool objGetAttrib(PKVM* vm, void* instance, PkStringPtr attrib);
|
||||
void objGetAttrib(PKVM* vm, void* instance, PkStringPtr attrib);
|
||||
|
||||
// The free callback of the object, that'll called by pocketlang when a
|
||||
// pocketlang native instance garbage collected.
|
||||
|
@ -171,19 +171,21 @@ typedef const char* (*pkInstNameFn) (uint32_t id);
|
||||
|
||||
// A get arribute callback, called by pocket VM when trying to get an attribute
|
||||
// from a native type. to return the value of the attribute use 'pkReturn...()'
|
||||
// functions, and return true. If the attribute not exists return false, But
|
||||
// DON'T set an error to the VM, this is necessary. Example if the '.as_string'
|
||||
// attribute doesn't exists, pocket VM will use a default to string value.
|
||||
typedef bool (*pkInstGetAttribFn) (PKVM* vm, void* instance,
|
||||
// functions. DON'T set an error to the VM if the attribute not exists. Example
|
||||
// if the '.as_string' attribute doesn't exists, pocket VM will use a default
|
||||
// to string value.
|
||||
typedef void (*pkInstGetAttribFn) (PKVM* vm, void* instance,
|
||||
PkStringPtr attrib);
|
||||
|
||||
// If the attribute dones't exists don't set an error, instead return false.
|
||||
// Pocket VM will handle it, however if the type of the value is incompatible
|
||||
// set an error with pkSetRuntimeError(); and return false, on success update
|
||||
// the native instance and return true. And do not ever use 'pkReturn...()'
|
||||
// function. This is a void return function.
|
||||
// Use pkGetArg...(vm, 0, ptr) function to get the value of the attribute
|
||||
// and use 0 as the argument index, using any other arg index value cause UB.
|
||||
//
|
||||
// If the attribute dones't exists DON'T set an error, instead return false.
|
||||
// Pocket VM will handle it, On success update the native instance and return
|
||||
// true. And DON'T ever use 'pkReturn...()' in the attribute setter It's is a
|
||||
// void return function.
|
||||
typedef bool (*pkInstSetAttribFn) (PKVM* vm, void* instance,
|
||||
PkStringPtr attrib, PkVar value);
|
||||
PkStringPtr attrib);
|
||||
|
||||
// A function callback symbol for clean/free the pkStringResult.
|
||||
typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result);
|
||||
@ -378,8 +380,10 @@ PK_PUBLIC PkVar pkGetArg(const PKVM* vm, int arg);
|
||||
// The functions below are used to extract the function arguments from the
|
||||
// stack as a type. They will first validate the argument's type and set a
|
||||
// runtime error if it's not and return false. Otherwise it'll set the [value]
|
||||
// with the extracted value. Note that the arguments are 1 based (to get the
|
||||
// first argument use 1 not 0).
|
||||
// with the extracted value.
|
||||
//
|
||||
// NOTE: The arguments are 1 based (to get the first argument use 1 not 0).
|
||||
// Only use arg index 0 to get the value of attribute setter call.
|
||||
|
||||
PK_PUBLIC bool pkGetArgBool(PKVM* vm, int arg, bool* value);
|
||||
PK_PUBLIC bool pkGetArgNumber(PKVM* vm, int arg, double* value);
|
||||
|
201
src/pk_core.c
201
src/pk_core.c
@ -114,21 +114,27 @@ PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
|
||||
} while(false)
|
||||
|
||||
// Check for errors in before calling the get arg public api function.
|
||||
#define CHECK_GET_ARG_API_ERRORS() \
|
||||
do { \
|
||||
__ASSERT(vm->fiber != NULL, \
|
||||
"This function can only be called at runtime."); \
|
||||
__ASSERT(arg > 0 && arg <= ARGC, "Invalid argument index."); \
|
||||
__ASSERT(value != NULL, "Argument [value] was NULL."); \
|
||||
#define CHECK_GET_ARG_API_ERRORS() \
|
||||
do { \
|
||||
__ASSERT(vm->fiber != NULL, \
|
||||
"This function can only be called at runtime."); \
|
||||
if (arg != 0) {/* If Native setter, the value would be at fiber->ret */ \
|
||||
__ASSERT(arg > 0 && arg <= ARGC, "Invalid argument index."); \
|
||||
} \
|
||||
__ASSERT(value != NULL, "Argument [value] was NULL."); \
|
||||
} while (false)
|
||||
|
||||
// Set error for incompatible type provided as an argument. (TODO: got type).
|
||||
#define ERR_INVALID_ARG_TYPE(m_type) \
|
||||
do { \
|
||||
char buff[STR_INT_BUFF_SIZE]; \
|
||||
sprintf(buff, "%d", arg); \
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$' at argument $.", \
|
||||
m_type, buff)); \
|
||||
#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)); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
// pkGetArgc implementation (see pocketlang.h for description).
|
||||
@ -654,22 +660,23 @@ DEF(coreStrSub,
|
||||
"the position and length of the substring are provided when this "
|
||||
"function is called. For example: `str_sub(str, pos, len)`.") {
|
||||
|
||||
int64_t len, pos;
|
||||
String* str;
|
||||
int64_t pos, len;
|
||||
|
||||
String* str = NULL;
|
||||
if (!validateArgString(vm, 1, &str)) return;
|
||||
if (!validateInteger(vm, ARG(2), &pos, "Argument 2")) return;
|
||||
if (!validateInteger(vm, ARG(3), &len, "Argument 3")) return;
|
||||
|
||||
if (str->length < pos || str->length < len)
|
||||
if (pos < 0 || str->length < pos)
|
||||
RET_ERR(newString(vm, "Index out of range."));
|
||||
|
||||
if (str->length < pos + len)
|
||||
RET_ERR(newString(vm, "Substring length exceeded the limit."));
|
||||
|
||||
// Edge case, empty string.
|
||||
if (len == 0) RET(VAR_OBJ(newStringLength(vm, "", 0)));
|
||||
|
||||
String* sub_str = newStringLength(vm, str->data + pos, (uint32_t)len);
|
||||
|
||||
RET(VAR_OBJ(sub_str));
|
||||
RET(VAR_OBJ(newStringLength(vm, str->data + pos, (uint32_t)len)));
|
||||
}
|
||||
|
||||
DEF(coreStrChr,
|
||||
@ -701,7 +708,6 @@ DEF(coreStrOrd,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// List functions.
|
||||
// ---------------
|
||||
|
||||
@ -1043,16 +1049,18 @@ DEF(stdMathArcTangent,
|
||||
}
|
||||
|
||||
DEF(stdMathLog10,
|
||||
"log10(value:num) -> num\n"
|
||||
"Return the logarithm to base 10 of argument [value]") {
|
||||
"log10(value:num) -> num\n"
|
||||
"Return the logarithm to base 10 of argument [value]") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
RET(VAR_NUM(log10(num)));
|
||||
}
|
||||
|
||||
DEF(stdMathRound,
|
||||
"round(value:num) -> num\n"
|
||||
) {
|
||||
"round(value:num) -> num\n"
|
||||
"Round to nearest integer, away from zero and return the number.") {
|
||||
|
||||
double num;
|
||||
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||
RET(VAR_NUM(round(num)));
|
||||
@ -1479,27 +1487,10 @@ bool varContains(PKVM* vm, Var elem, Var container) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
// Here we're switching the FNV-1a hash value of the name (cstring). Which is
|
||||
// an efficient way than having multiple if (attrib == "name"). From O(n) * k
|
||||
// to O(1) where n is the length of the string and k is the number of string
|
||||
// comparison.
|
||||
//
|
||||
// ex:
|
||||
// SWITCH_ATTRIB(str) { // str = "length"
|
||||
// CASE_ATTRIB("length", 0x83d03615) : { return string->length; }
|
||||
// }
|
||||
//
|
||||
// In C++11 this can be achieved (in a better way) with user defined literals
|
||||
// and constexpr. (Reference from my previous compiler written in C++).
|
||||
// https://github.com/ThakeeNathees/carbon/
|
||||
//
|
||||
// However there is a python script that's matching the CASE_ATTRIB() macro
|
||||
// calls and validate if the string and the hash values are matching.
|
||||
// TODO: port it to the CI/CD process at github actions.
|
||||
//
|
||||
#define SWITCH_ATTRIB(name) switch (utilHashString(name))
|
||||
#define CASE_ATTRIB(name, hash) case hash
|
||||
#define CASE_DEFAULT default
|
||||
// TODO: The ERR_NO_ATTRIB() macro should splited into 2 to for setter and
|
||||
// getter and for the setter, the error message should be "object has no
|
||||
// mutable attribute", to indicate that there might be an attribute with the
|
||||
// name might be exists (but not accessed in a setter).
|
||||
|
||||
// Set error for accessing non-existed attribute.
|
||||
#define ERR_NO_ATTRIB(vm, on, attrib) \
|
||||
@ -1519,21 +1510,21 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
case OBJ_STRING:
|
||||
{
|
||||
String* str = (String*)obj;
|
||||
SWITCH_ATTRIB(attrib->data) {
|
||||
switch (attrib->hash) {
|
||||
|
||||
CASE_ATTRIB("length", 0x83d03615) :
|
||||
case CHECK_HASH("length", 0x83d03615):
|
||||
return VAR_NUM((double)(str->length));
|
||||
|
||||
CASE_ATTRIB("lower", 0xb51d04ba) :
|
||||
case CHECK_HASH("lower", 0xb51d04ba):
|
||||
return VAR_OBJ(stringLower(vm, str));
|
||||
|
||||
CASE_ATTRIB("upper", 0xa8c6a47) :
|
||||
case CHECK_HASH("upper", 0xa8c6a47):
|
||||
return VAR_OBJ(stringUpper(vm, str));
|
||||
|
||||
CASE_ATTRIB("strip", 0xfd1b18d1) :
|
||||
case CHECK_HASH("strip", 0xfd1b18d1):
|
||||
return VAR_OBJ(stringStrip(vm, str));
|
||||
|
||||
CASE_DEFAULT:
|
||||
default:
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return VAR_NULL;
|
||||
}
|
||||
@ -1544,12 +1535,12 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
case OBJ_LIST:
|
||||
{
|
||||
List* list = (List*)obj;
|
||||
SWITCH_ATTRIB(attrib->data) {
|
||||
switch (attrib->hash) {
|
||||
|
||||
CASE_ATTRIB("length", 0x83d03615) :
|
||||
case CHECK_HASH("length", 0x83d03615):
|
||||
return VAR_NUM((double)(list->elements.count));
|
||||
|
||||
CASE_DEFAULT:
|
||||
default:
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return VAR_NULL;
|
||||
}
|
||||
@ -1570,22 +1561,22 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
case OBJ_RANGE:
|
||||
{
|
||||
Range* range = (Range*)obj;
|
||||
SWITCH_ATTRIB(attrib->data) {
|
||||
switch (attrib->hash) {
|
||||
|
||||
CASE_ATTRIB("as_list", 0x1562c22):
|
||||
case CHECK_HASH("as_list", 0x1562c22):
|
||||
return VAR_OBJ(rangeAsList(vm, range));
|
||||
|
||||
// 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.
|
||||
|
||||
CASE_ATTRIB("first", 0x4881d841):
|
||||
case CHECK_HASH("first", 0x4881d841):
|
||||
return VAR_NUM(range->from);
|
||||
|
||||
CASE_ATTRIB("last", 0x63e1d819):
|
||||
case CHECK_HASH("last", 0x63e1d819):
|
||||
return VAR_NUM(range->to);
|
||||
|
||||
CASE_DEFAULT:
|
||||
default:
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return VAR_NULL;
|
||||
}
|
||||
@ -1625,15 +1616,15 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
case OBJ_FUNC:
|
||||
{
|
||||
Function* fn = (Function*)obj;
|
||||
SWITCH_ATTRIB(attrib->data) {
|
||||
switch (attrib->hash) {
|
||||
|
||||
CASE_ATTRIB("arity", 0x3e96bd7a) :
|
||||
case CHECK_HASH("arity", 0x3e96bd7a):
|
||||
return VAR_NUM((double)(fn->arity));
|
||||
|
||||
CASE_ATTRIB("name", 0x8d39bde6) :
|
||||
case CHECK_HASH("name", 0x8d39bde6):
|
||||
return VAR_OBJ(newString(vm, fn->name));
|
||||
|
||||
CASE_DEFAULT:
|
||||
default:
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return VAR_NULL;
|
||||
}
|
||||
@ -1643,15 +1634,15 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
case OBJ_FIBER:
|
||||
{
|
||||
Fiber* fb = (Fiber*)obj;
|
||||
SWITCH_ATTRIB(attrib->data) {
|
||||
switch (attrib->hash) {
|
||||
|
||||
CASE_ATTRIB("is_done", 0x789c2706):
|
||||
case CHECK_HASH("is_done", 0x789c2706):
|
||||
return VAR_BOOL(fb->state == FIBER_DONE);
|
||||
|
||||
CASE_ATTRIB("function", 0x9ed64249):
|
||||
case CHECK_HASH("function", 0x9ed64249):
|
||||
return VAR_OBJ(fb->func);
|
||||
|
||||
CASE_DEFAULT:
|
||||
default:
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return VAR_NULL;
|
||||
}
|
||||
@ -1664,48 +1655,12 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
|
||||
case OBJ_INST:
|
||||
{
|
||||
Instance* inst = (Instance*)obj;
|
||||
if (inst->is_native) {
|
||||
|
||||
if (vm->config.inst_get_attrib_fn) {
|
||||
|
||||
// Temproarly change the fiber's "return address" to points to the
|
||||
// below var 'ret' so that the users can use 'pkReturn...()' function
|
||||
// to return the attribute as well.
|
||||
Var* temp = vm->fiber->ret;
|
||||
Var ret = VAR_NULL;
|
||||
vm->fiber->ret = &ret;
|
||||
|
||||
PkStringPtr attr = { attrib->data, NULL, NULL,
|
||||
attrib->length, attrib->hash };
|
||||
if (!vm->config.inst_get_attrib_fn(vm, inst->native, attr)) {
|
||||
// TODO: if the attribute is '.as_string' return repr.
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
vm->fiber->ret = temp;
|
||||
return ret;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// TODO: Optimize this with binary search.
|
||||
Class* ty = inst->ins->type;
|
||||
for (uint32_t i = 0; i < ty->field_names.count; i++) {
|
||||
ASSERT_INDEX(i, ty->field_names.count);
|
||||
ASSERT_INDEX(ty->field_names.data[i], ty->owner->names.count);
|
||||
String* f_name = ty->owner->names.data[ty->field_names.data[i]];
|
||||
if (f_name->hash == attrib->hash &&
|
||||
f_name->length == attrib->length &&
|
||||
memcmp(f_name->data, attrib->data, attrib->length) == 0) {
|
||||
return inst->ins->fields.data[i];
|
||||
}
|
||||
}
|
||||
Var value;
|
||||
if (!instGetAttrib(vm, (Instance*)obj, attrib, &value)) {
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return VAR_NULL;
|
||||
return value;
|
||||
}
|
||||
|
||||
default:
|
||||
@ -1811,31 +1766,16 @@ do { \
|
||||
|
||||
case OBJ_INST:
|
||||
{
|
||||
Instance* inst = (Instance*)obj;
|
||||
if (inst->is_native) {
|
||||
TODO;
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
// TODO: Optimize this with binary search.
|
||||
Class* ty = inst->ins->type;
|
||||
for (uint32_t i = 0; i < ty->field_names.count; i++) {
|
||||
ASSERT_INDEX(i, ty->field_names.count);
|
||||
ASSERT_INDEX(ty->field_names.data[i], ty->owner->names.count);
|
||||
String* f_name = ty->owner->names.data[ty->field_names.data[i]];
|
||||
if (f_name->hash == attrib->hash &&
|
||||
f_name->length == attrib->length &&
|
||||
memcmp(f_name->data, attrib->data, attrib->length) == 0) {
|
||||
inst->ins->fields.data[i] = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
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;
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
// If we reached here, that means the attribute exists and we have
|
||||
// updated the value.
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
@ -1846,9 +1786,6 @@ do { \
|
||||
#undef ATTRIB_IMMUTABLE
|
||||
}
|
||||
|
||||
#undef SWITCH_ATTRIB
|
||||
#undef CASE_ATTRIB
|
||||
#undef CASE_DEFAULT
|
||||
#undef ERR_NO_ATTRIB
|
||||
|
||||
Var varGetSubscript(PKVM* vm, Var on, Var key) {
|
||||
|
@ -78,4 +78,28 @@
|
||||
#define DEALLOCATE(vm, pointer) \
|
||||
vmRealloc(vm, pointer, 0, 0)
|
||||
|
||||
/*****************************************************************************/
|
||||
/* REUSABLE INTERNAL MACROS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Here we're switching the FNV-1a hash value of the name (cstring). Which is
|
||||
// an efficient way than having multiple if (attrib == "name"). From O(n) * k
|
||||
// to O(1) where n is the length of the string and k is the number of string
|
||||
// comparison.
|
||||
//
|
||||
// ex:
|
||||
// switch (attrib->hash) { // str = "length"
|
||||
// case CHECK_HASH("length", 0x83d03615) : { return string->length; }
|
||||
// }
|
||||
//
|
||||
// In C++11 this can be achieved (in a better way) with user defined literals
|
||||
// and constexpr. (Reference from my previous compiler written in C++).
|
||||
// https://github.com/ThakeeNathees/carbon/
|
||||
//
|
||||
// However there is a python script that's matching the CHECK_HASH() macro
|
||||
// calls and validate if the string and the hash values are matching.
|
||||
// TODO: port it to the CI/CD process at github actions.
|
||||
//
|
||||
#define CHECK_HASH(name, hash) hash
|
||||
|
||||
#endif // PK_INTERNAL
|
||||
|
124
src/pk_var.c
124
src/pk_var.c
@ -1143,6 +1143,127 @@ uint32_t scriptAddGlobal(PKVM* vm, Script* script,
|
||||
return script->globals.count - 1;
|
||||
}
|
||||
|
||||
bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value) {
|
||||
ASSERT(inst != NULL, OOPS);
|
||||
ASSERT(attrib != NULL, OOPS);
|
||||
ASSERT(value != NULL, OOPS);
|
||||
|
||||
// This function should only be called at runtime.
|
||||
ASSERT(vm->fiber != NULL, OOPS);
|
||||
|
||||
if (inst->is_native) {
|
||||
|
||||
if (vm->config.inst_get_attrib_fn) {
|
||||
// Temproarly change the fiber's "return address" to points to the
|
||||
// below var 'val' so that the users can use 'pkReturn...()' function
|
||||
// to return the attribute as well.
|
||||
Var* temp = vm->fiber->ret;
|
||||
Var val = VAR_UNDEFINED;
|
||||
|
||||
vm->fiber->ret = &val;
|
||||
PkStringPtr attr = { attrib->data, NULL, NULL,
|
||||
attrib->length, attrib->hash };
|
||||
vm->config.inst_get_attrib_fn(vm, inst->native, attr);
|
||||
vm->fiber->ret = temp;
|
||||
|
||||
if (IS_UNDEF(val)) {
|
||||
|
||||
// FIXME: add a list of attribute overrides.
|
||||
if (IS_CSTR_EQ(attrib, "as_string", 9,
|
||||
CHECK_HASH("as_string", 0xbdef4147))) {
|
||||
*value = VAR_OBJ(toRepr(vm, VAR_OBJ(inst)));
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we reached here, the native instance don't have the attribute
|
||||
// and no overriden attributes found, return false to indicate that the
|
||||
// attribute doesn't exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attribute [val] updated by the hosting application.
|
||||
*value = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the hosting application doesn't provided a getter function, we treat
|
||||
// it as if the instance don't has the attribute.
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
// TODO: Optimize this with binary search.
|
||||
Class* ty = inst->ins->type;
|
||||
for (uint32_t i = 0; i < ty->field_names.count; i++) {
|
||||
ASSERT_INDEX(i, ty->field_names.count);
|
||||
ASSERT_INDEX(ty->field_names.data[i], ty->owner->names.count);
|
||||
String* f_name = ty->owner->names.data[ty->field_names.data[i]];
|
||||
if (IS_STR_EQ(f_name, attrib)) {
|
||||
*value = inst->ins->fields.data[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find the attribute in it's type class, return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value) {
|
||||
|
||||
if (inst->is_native) {
|
||||
|
||||
if (vm->config.inst_set_attrib_fn) {
|
||||
// Temproarly change the fiber's "return address" to points to the
|
||||
// below var 'ret' so that the users can use 'pkGetArg...()' function
|
||||
// to validate and get the attribute.
|
||||
Var* temp = vm->fiber->ret;
|
||||
Var attrib_ptr = value;
|
||||
|
||||
vm->fiber->ret = &attrib_ptr;
|
||||
PkStringPtr attr = { attrib->data, NULL, NULL,
|
||||
attrib->length, attrib->hash };
|
||||
bool exists = vm->config.inst_set_attrib_fn(vm, inst->native, attr);
|
||||
vm->fiber->ret = temp;
|
||||
|
||||
// If the type is incompatible there'll be an error by now, return false
|
||||
// and the user of this function has to check VM_HAS_ERROR() as well.
|
||||
if (VM_HAS_ERROR(vm)) return false;
|
||||
|
||||
// If the attribute exists on the native type, the host application would
|
||||
// returned true by now, return it.
|
||||
return exists;
|
||||
}
|
||||
|
||||
// If the host application doesn't provided a setter we treat it as it
|
||||
// doesn't has the attribute.
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
// TODO: Optimize this with binary search.
|
||||
Class* ty = inst->ins->type;
|
||||
for (uint32_t i = 0; i < ty->field_names.count; i++) {
|
||||
ASSERT_INDEX(i, ty->field_names.count);
|
||||
ASSERT_INDEX(ty->field_names.data[i], ty->owner->names.count);
|
||||
String* f_name = ty->owner->names.data[ty->field_names.data[i]];
|
||||
if (f_name->hash == attrib->hash &&
|
||||
f_name->length == attrib->length &&
|
||||
memcmp(f_name->data, attrib->data, attrib->length) == 0) {
|
||||
inst->ins->fields.data[i] = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find the attribute in it's type class, return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* UTILITY FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
@ -1473,13 +1594,13 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff,
|
||||
pkByteBufferWrite(buff, vm, '[');
|
||||
pkByteBufferAddString(buff, vm, inst->name,
|
||||
(uint32_t)strlen(inst->name));
|
||||
pkByteBufferWrite(buff, vm, ':');
|
||||
|
||||
if (!inst->is_native) {
|
||||
const Class* ty = inst->ins->type;
|
||||
const Inst* ins = inst->ins;
|
||||
ASSERT(ins->fields.count == ty->field_names.count, OOPS);
|
||||
|
||||
pkByteBufferWrite(buff, vm, ':');
|
||||
for (uint32_t i = 0; i < ty->field_names.count; i++) {
|
||||
if (i != 0) pkByteBufferWrite(buff, vm, ',');
|
||||
|
||||
@ -1490,7 +1611,6 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff,
|
||||
_toStringInternal(vm, ins->fields.data[i], buff, outer, repr);
|
||||
}
|
||||
} else {
|
||||
pkByteBufferWrite(buff, vm, ':');
|
||||
|
||||
char buff_addr[STR_HEX_BUFF_SIZE];
|
||||
char* ptr = (char*)buff_addr;
|
||||
|
24
src/pk_var.h
24
src/pk_var.h
@ -137,8 +137,22 @@
|
||||
#define IS_INT(value) ((value & _MASK_INTEGER) == _MASK_INTEGER)
|
||||
#define IS_NUM(value) ((value & _MASK_QNAN) != _MASK_QNAN)
|
||||
#define IS_OBJ(value) ((value & _MASK_OBJECT) == _MASK_OBJECT)
|
||||
|
||||
// Evaluate to true if the var is an object and type of [obj_type].
|
||||
#define IS_OBJ_TYPE(var, obj_type) IS_OBJ(var) && AS_OBJ(var)->type == obj_type
|
||||
|
||||
// Check if the 2 pocket strings are equal.
|
||||
#define IS_STR_EQ(s1, s2) \
|
||||
(((s1)->hash == (s2)->hash) && \
|
||||
((s1)->length == (s2)->length) && \
|
||||
(memcmp((const void*)(s1)->data, (const void*)(s2)->data, (s1)->length) == 0))
|
||||
|
||||
// Compare pocket string with c string.
|
||||
#define IS_CSTR_EQ(str, cstr, len, chash) \
|
||||
(((str)->hash == chash) && \
|
||||
((str)->length == len) && \
|
||||
(memcmp((const void*)(str)->data, (const void*)(cstr), len) == 0))
|
||||
|
||||
// Decode types.
|
||||
#define AS_BOOL(value) ((value) == VAR_TRUE)
|
||||
#define AS_INT(value) ((int32_t)((value) & _PAYLOAD_INTEGER))
|
||||
@ -570,6 +584,16 @@ uint32_t scriptAddGlobal(PKVM* vm, Script* script,
|
||||
const char* name, uint32_t length,
|
||||
Var value);
|
||||
|
||||
// Get the attribut from the instance and set it [value]. On success return
|
||||
// true, if the attribute not exists it'll return false but won't set an error.
|
||||
bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value);
|
||||
|
||||
// Set the attribute to the instance and return true on success, if the
|
||||
// attribute doesn't exists it'll return false but if the [value] type is
|
||||
// incompatible, this will set an error to the VM, which you can check with
|
||||
// VM_HAS_ERROR() macro function.
|
||||
bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value);
|
||||
|
||||
// Release all the object owned by the [self] including itself.
|
||||
void freeObject(PKVM* vm, Object* self);
|
||||
|
||||
|
@ -28,6 +28,7 @@ def to_tolevel_path(path):
|
||||
## corresponding cstring in the CASE_ATTRIB(name, hash) macro calls.
|
||||
HASH_CHECK_LIST = [
|
||||
"../src/pk_core.c",
|
||||
"../src/pk_var.c",
|
||||
]
|
||||
|
||||
## A list of extension to perform static checks, of all the files in the
|
||||
@ -68,7 +69,7 @@ def main():
|
||||
print("Static checks were passed.")
|
||||
|
||||
def check_fnv1_hash(sources):
|
||||
PATTERN = r'CASE_ATTRIB\(\s*"([A-Za-z0-9_]+)"\s*,\s*(0x[0-9abcdef]+)\)'
|
||||
PATTERN = r'CHECK_HASH\(\s*"([A-Za-z0-9_]+)"\s*,\s*(0x[0-9abcdef]+)\)'
|
||||
for file in sources:
|
||||
fp = open(file, 'r')
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
## Core builtin functions and attribute tests.
|
||||
|
||||
## Math functions
|
||||
from math import *
|
||||
|
||||
assert(hex(12648430) == '0xc0ffee')
|
||||
assert(hex(255) == '0xff' and hex(10597059) == '0xa1b2c3')
|
||||
assert(hex(-4294967295) == '-0xffffffff') ## the largest.
|
||||
|
||||
|
||||
## string attributes.
|
||||
assert(''.length == 0)
|
||||
assert('test'.length == 4)
|
||||
@ -46,10 +48,6 @@ assert(r.as_list == [1, 2, 3, 4])
|
||||
assert(r.first == 1)
|
||||
assert(r.last == 5)
|
||||
|
||||
## Math functions
|
||||
from math import PI, sin, cos, tan, abs, sinh,
|
||||
cosh, tanh, asin, acos, atan
|
||||
|
||||
assert(sin(0) == 0)
|
||||
assert(sin(PI/2) == 1)
|
||||
|
||||
@ -81,6 +79,11 @@ for i in 0..interval-1
|
||||
assert(abs(tan(atan(x)) - x) < threshold)
|
||||
end
|
||||
|
||||
assert(abs(log10(2) - 0.3010299956639812) < threshold)
|
||||
assert(round(1.4) == 1)
|
||||
assert(round(1.5) == 2)
|
||||
assert(round(-1.5) == -2)
|
||||
|
||||
# If we got here, that means all test were passed.
|
||||
print('All TESTS PASSED')
|
||||
|
||||
|
26
tests/native/README.md
Normal file
26
tests/native/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
## Example on how to integrate pocket VM with in your application.
|
||||
### This readme is incomplete (WIP)
|
||||
|
||||
- Including this example this repository contains 3 examples on how to integrate
|
||||
pocket VM with your application
|
||||
- This example
|
||||
- The `cli/` application
|
||||
- The `docs/try/main.c` web assembly version of pocketlang
|
||||
|
||||
|
||||
- To integrate pocket VM in you project:
|
||||
- Just drag and drop the `src/` directory in your project
|
||||
- Add all C files in the directory with your source files
|
||||
- Add `src/include` to the include path
|
||||
- Compile
|
||||
|
||||
----
|
||||
|
||||
Compile the `example.c` file which contains examples on how to write your custom module
|
||||
to pocketlang, using the below command.
|
||||
|
||||
```
|
||||
gcc example.c -o example ../../src/*.c -I../../src/include -lm
|
||||
```
|
||||
|
||||
Note that only calling C from pocket is completed and calling pocket from C is WIP.
|
222
tests/native/example.c
Normal file
222
tests/native/example.c
Normal file
@ -0,0 +1,222 @@
|
||||
|
||||
#include <pocketlang.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
// The script we're using to test the native Vector type.
|
||||
static const char* code = " \n\
|
||||
import Vector # The native module. \n\
|
||||
print('Module =', Vector) \n\
|
||||
\n\
|
||||
vec1 = Vector.new(1, 2) # Calling native method. \n\
|
||||
print('vec1 =', 'Vector.new(1, 2)') \n\
|
||||
print() \n\
|
||||
\n\
|
||||
# Using the native getter. \n\
|
||||
print('vec1.x =', vec1.x) \n\
|
||||
print('vec1.y =', vec1.y) \n\
|
||||
print('vec1.length =', vec1.length) \n\
|
||||
print() \n\
|
||||
\n\
|
||||
# Using the native setter. \n\
|
||||
vec1.x = 3; vec1.y = 4; \n\
|
||||
print('vec1.x =', vec1.x) \n\
|
||||
print('vec1.y =', vec1.y) \n\
|
||||
print('vec1.length =', vec1.length) \n\
|
||||
print() \n\
|
||||
\n\
|
||||
vec2 = Vector.new(5, 6) \n\
|
||||
vec3 = Vector.add(vec1, vec2) \n\
|
||||
print('vec3 =', 'Vector.add(vec1, vec2)') \n\
|
||||
print('vec3.x =', vec3.x) \n\
|
||||
print('vec3.y =', vec3.y) \n\
|
||||
\n\
|
||||
";
|
||||
|
||||
/*****************************************************************************/
|
||||
/* NATIVE TYPE DEFINES & CALLBACKS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// An enum value of native object, used as unique of the type in pocketlang.
|
||||
typedef enum {
|
||||
OBJ_VECTOR = 0,
|
||||
} ObjType;
|
||||
|
||||
typedef struct {
|
||||
ObjType type;
|
||||
} Obj;
|
||||
|
||||
typedef struct {
|
||||
Obj base; // "Inherits" objects.
|
||||
double x, y; // Vector variables.
|
||||
} Vector;
|
||||
|
||||
// Get name callback, will called from pocketlang to get the type name from
|
||||
// the ID (the enum value).
|
||||
const char* getObjName(uint32_t id) {
|
||||
switch ((ObjType)id) {
|
||||
case OBJ_VECTOR: return "Vector";
|
||||
}
|
||||
return NULL; // Unreachable.
|
||||
}
|
||||
|
||||
// Instance getter callback to get a value from the native instance.
|
||||
// The hash value and the length of the string are provided with the
|
||||
// argument [attrib].
|
||||
void objGetAttrib(PKVM* vm, void* instance, PkStringPtr attrib) {
|
||||
|
||||
Obj* obj = (Obj*)instance;
|
||||
|
||||
switch (obj->type) {
|
||||
case OBJ_VECTOR: {
|
||||
Vector* vector = ((Vector*)obj);
|
||||
if (strcmp(attrib.string, "x") == 0) {
|
||||
pkReturnNumber(vm, vector->x);
|
||||
return;
|
||||
|
||||
} else if (strcmp(attrib.string, "y") == 0) {
|
||||
pkReturnNumber(vm, vector->y);
|
||||
return;
|
||||
|
||||
} else if (strcmp(attrib.string, "length") == 0) {
|
||||
double length = sqrt(pow(vector->x, 2) + pow(vector->y, 2));
|
||||
pkReturnNumber(vm, length);
|
||||
return;
|
||||
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
// If we reached here that means the attribute doesn't exists.
|
||||
// Since we haven't used pkReturn...() function, pocket VM already
|
||||
// know that the attribute doesn't exists. just return.
|
||||
return;
|
||||
}
|
||||
|
||||
// Instance setter callback to set the value to the native instance.
|
||||
// The hash value and the length of the string are provided with the
|
||||
// argument [attrib].
|
||||
bool objSetAttrib(PKVM* vm, void* instance, PkStringPtr attrib) {
|
||||
|
||||
Obj* obj = (Obj*)instance;
|
||||
|
||||
switch (obj->type) {
|
||||
case OBJ_VECTOR: {
|
||||
if (strcmp(attrib.string, "x") == 0) {
|
||||
double x; // Get the number x.
|
||||
if (!pkGetArgNumber(vm, 0, &x)) return false;
|
||||
((Vector*)obj)->x = x;
|
||||
return true;
|
||||
|
||||
} else if (strcmp(attrib.string, "y") == 0) {
|
||||
double y; // Get the number x.
|
||||
if (!pkGetArgNumber(vm, 0, &y)) return false;
|
||||
((Vector*)obj)->y = y;
|
||||
return true;
|
||||
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
// If we reached here that means the attribute doesn't exists.
|
||||
// Return false to indicate it.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The free object callback, called just before the native instance, garbage
|
||||
// collect.
|
||||
void freeObj(PKVM* vm, void* instance) {
|
||||
Obj* obj = (Obj*)instance;
|
||||
// Your cleanups.
|
||||
free((void*)obj);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* VECTOR MODULE FUNCTIONS REGISTER */
|
||||
/*****************************************************************************/
|
||||
|
||||
// The Vector.new(x, y) function.
|
||||
void _vecNew(PKVM* vm) {
|
||||
double x, y; // The args.
|
||||
|
||||
// Get the args from the stack, If it's not number, return.
|
||||
if (!pkGetArgNumber(vm, 1, &x)) return;
|
||||
if (!pkGetArgNumber(vm, 2, &y)) return;
|
||||
|
||||
// Create a new vector.
|
||||
Vector* vec = (Vector*)malloc(sizeof(Vector));
|
||||
vec->base.type = OBJ_VECTOR;
|
||||
vec->x = x, vec->y = y;
|
||||
|
||||
pkReturnInstNative(vm, (void*)vec, OBJ_VECTOR);
|
||||
}
|
||||
|
||||
// The Vector.length(vec) function.
|
||||
void _vecAdd(PKVM* vm) {
|
||||
Vector *v1, *v2;
|
||||
if (!pkGetArgInst(vm, 1, OBJ_VECTOR, (void**)&v1)) return;
|
||||
if (!pkGetArgInst(vm, 2, OBJ_VECTOR, (void**)&v2)) return;
|
||||
|
||||
// Create a new vector.
|
||||
Vector* v3 = (Vector*)malloc(sizeof(Vector));
|
||||
v3->base.type = OBJ_VECTOR;
|
||||
v3->x = v1->x + v2->x;
|
||||
v3->y = v1->y + v2->y;
|
||||
|
||||
pkReturnInstNative(vm, (void*)v3, OBJ_VECTOR);
|
||||
}
|
||||
|
||||
// Register the 'Vector' module and it's functions.
|
||||
void registerVector(PKVM* vm) {
|
||||
PkHandle* vector = pkNewModule(vm, "Vector");
|
||||
|
||||
pkModuleAddFunction(vm, vector, "new", _vecNew, 2);
|
||||
pkModuleAddFunction(vm, vector, "add", _vecAdd, 2);
|
||||
|
||||
pkReleaseHandle(vm, vector);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKET VM CALLBACKS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Error report callback.
|
||||
void reportError(PKVM* vm, PkErrorType type,
|
||||
const char* file, int line,
|
||||
const char* message) {
|
||||
fprintf(stderr, "Error: %s\n", message);
|
||||
}
|
||||
|
||||
// print() callback to write stdout.
|
||||
void stdoutWrite(PKVM* vm, const char* text) {
|
||||
fprintf(stdout, "%s", text);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
PkConfiguration config = pkNewConfiguration();
|
||||
config.error_fn = reportError;
|
||||
config.write_fn = stdoutWrite;
|
||||
//config.read_fn = stdinRead;
|
||||
config.inst_free_fn = freeObj;
|
||||
config.inst_name_fn = getObjName;
|
||||
config.inst_get_attrib_fn = objGetAttrib;
|
||||
config.inst_set_attrib_fn = objSetAttrib;
|
||||
//config.load_script_fn = loadScript;
|
||||
//config.resolve_path_fn = resolvePath;
|
||||
|
||||
PKVM* vm = pkNewVM(&config);
|
||||
registerVector(vm);
|
||||
|
||||
PkStringPtr source = { code, NULL, NULL };
|
||||
PkStringPtr path = { "./some/path/", NULL, NULL };
|
||||
|
||||
PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/);
|
||||
pkFreeVM(vm);
|
||||
|
||||
return result;
|
||||
}
|
Loading…
Reference in New Issue
Block a user