From 6ad5dfe9f7a02bbd837f3bae784d66b503b0616d Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Wed, 30 Jun 2021 12:09:17 +0530 Subject: [PATCH] native api function improvements (#153) --- cli/modules.c | 19 +++- cli/modules.h | 2 +- src/include/pocketlang.h | 28 ++--- src/pk_core.c | 201 ++++++++++++----------------------- src/pk_internal.h | 24 +++++ src/pk_var.c | 124 +++++++++++++++++++++- src/pk_var.h | 24 +++++ tests/check.py | 3 +- tests/lang/core.pk | 13 ++- tests/native/README.md | 26 +++++ tests/native/example.c | 222 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 530 insertions(+), 156 deletions(-) create mode 100644 tests/native/README.md create mode 100644 tests/native/example.c diff --git a/cli/modules.c b/cli/modules.c index 5afb3aa..56ca770 100644 --- a/cli/modules.c +++ b/cli/modules.c @@ -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) { diff --git a/cli/modules.h b/cli/modules.h index 1bc1672..404e1bf 100644 --- a/cli/modules.h +++ b/cli/modules.h @@ -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. diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index d568bde..6fdc97e 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -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); diff --git a/src/pk_core.c b/src/pk_core.c index 8c79bef..903b6fd 100644 --- a/src/pk_core.c +++ b/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) { diff --git a/src/pk_internal.h b/src/pk_internal.h index c65f26f..dceafe9 100644 --- a/src/pk_internal.h +++ b/src/pk_internal.h @@ -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 diff --git a/src/pk_var.c b/src/pk_var.c index c792364..9ab4db8 100644 --- a/src/pk_var.c +++ b/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; diff --git a/src/pk_var.h b/src/pk_var.h index b12ee64..cf62f90 100644 --- a/src/pk_var.h +++ b/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); diff --git a/tests/check.py b/tests/check.py index 0c9cc26..38a85b2 100644 --- a/tests/check.py +++ b/tests/check.py @@ -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') diff --git a/tests/lang/core.pk b/tests/lang/core.pk index 6e07acc..9bd5fae 100644 --- a/tests/lang/core.pk +++ b/tests/lang/core.pk @@ -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') diff --git a/tests/native/README.md b/tests/native/README.md new file mode 100644 index 0000000..5e4376c --- /dev/null +++ b/tests/native/README.md @@ -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. diff --git a/tests/native/example.c b/tests/native/example.c new file mode 100644 index 0000000..dd41538 --- /dev/null +++ b/tests/native/example.c @@ -0,0 +1,222 @@ + +#include + +#include +#include +#include +#include + +// 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; +}