native api function improvements (#153)

This commit is contained in:
Thakee Nathees 2021-06-30 12:09:17 +05:30 committed by GitHub
parent b35b7a48cc
commit 6ad5dfe9f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 530 additions and 156 deletions

View File

@ -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) {

View File

@ -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.

View File

@ -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);

View File

@ -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) {

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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')

View File

@ -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
View 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
View 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;
}