From 29be68fc8686960ab8d473accf01c8454195143c Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Wed, 20 Apr 2022 13:54:45 +0530 Subject: [PATCH 1/9] classes were removed temproarly to implement classes properly and support methods I had to remove the older class syntax temproarly. --- src/pk_compiler.c | 80 ++++++-------------------------------------- src/pk_core.c | 4 ++- src/pk_debug.c | 11 +++--- src/pk_opcodes.h | 3 ++ src/pk_value.c | 37 ++++++++------------ src/pk_value.h | 25 +++++++------- src/pk_vm.c | 9 +++++ tests/lang/class.pk | 31 ++--------------- tests/lang/fibers.pk | 22 ++++++------ tests/lang/import.pk | 4 --- 10 files changed, 72 insertions(+), 154 deletions(-) diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 064ad5c..d4c9835 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -2271,88 +2271,28 @@ static void compileBlockBody(Compiler* compiler, BlockType type); // Compile a class and return it's index in the module's types buffer. static void compileClass(Compiler* compiler) { + ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS); + + TODO; //< compileClass Function is in-compilete. + // Consume the name of the type. consume(compiler, TK_NAME, "Expected a type name."); const char* name = compiler->parser.previous.start; int name_len = compiler->parser.previous.length; + int name_line = compiler->parser.previous.line; // Create a new class. - int cls_index, ctor_index; - Class* cls = newClass(compiler->parser.vm, compiler->module, - name, (uint32_t)name_len, &cls_index, &ctor_index); - cls->ctor->fn->arity = 0; - - // FIXME: - // Temproary patch for moving functions and classes to constant buffer. - ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS); - int index = compilerAddVariable(compiler, - compiler->parser.previous.start, - compiler->parser.previous.length, - compiler->parser.previous.line); - moduleSetGlobal(compiler->module, index, VAR_OBJ(cls)); + int cls_index; + Class* cls = newClass(compiler->parser.vm, name, name_len, + compiler->module, NULL, &cls_index); // Check count exceeded. checkMaxConstantsReached(compiler, cls_index); - checkMaxConstantsReached(compiler, ctor_index); - // Compile the constructor function. - ASSERT(compiler->func->ptr == compiler->module->body->fn, OOPS); - Func curr_fn; - compilerPushFunc(compiler, &curr_fn, cls->ctor->fn); - compilerEnterBlock(compiler); + // Compile all the methods and constructors. + TODO; - // Push an instance on the stack. - emitOpcode(compiler, OP_PUSH_INSTANCE); - emitShort(compiler, cls_index); - - skipNewLines(compiler); - TokenType next = peek(compiler); - while (next != TK_END && next != TK_EOF) { - - // Compile field name. - consume(compiler, TK_NAME, "Expected a type name."); - const char* f_name = compiler->parser.previous.start; - int f_len = compiler->parser.previous.length; - - int f_index = 0; - String* new_name = moduleAddString(compiler->module, compiler->parser.vm, - f_name, f_len, &f_index); - - for (uint32_t i = 0; i < cls->field_names.count; i++) { - String* prev = moduleGetStringAt(compiler->module, - cls->field_names.data[i]); - ASSERT(prev != NULL, OOPS); - if (IS_STR_EQ(new_name, prev)) { - parseError(compiler, "Class field with name '%s' already exists.", - new_name->data); - } - } - - pkUintBufferWrite(&cls->field_names, compiler->parser.vm, f_index); - - // Consume the assignment expression. - consume(compiler, TK_EQ, "Expected an assignment after field name."); - compileExpression(compiler); // Assigned value. - consumeEndStatement(compiler); - - // At this point the stack top would be the expression. - emitOpcode(compiler, OP_INST_APPEND); - - skipNewLines(compiler); - next = peek(compiler); - } consume(compiler, TK_END, "Expected 'end' after a class declaration end."); - - // The instance pushed by the OP_PUSH_INSTANCE instruction is at the top - // of the stack, return it (Constructor will return the instance). Note that - // the emitFunctionEnd function will also add a return instruction but that's - // for functions which doesn't return anything explicitly. This return won't - // change compiler's stack size because it won't pop the return value. - emitOpcode(compiler, OP_RETURN); - - compilerExitBlock(compiler); - emitFunctionEnd(compiler); - compilerPopFunc(compiler); } // Compile a function and return it's index in the module's function buffer. diff --git a/src/pk_core.c b/src/pk_core.c index b10b7e6..82b07ad 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -1205,7 +1205,9 @@ static void initializeCoreModules(PKVM* vm) { // modify the PI, like in python. moduleAddGlobal(vm, math, "PI", 2, VAR_NUM(M_PI)); - NEW_MODULE(fiber, "Fiber"); + // FIXME: + // Temproarly rename the fiber to move it to the builtin type classes. + NEW_MODULE(fiber, "_Fiber"); MODULE_ADD_FN(fiber, "new", stdFiberNew, 1); MODULE_ADD_FN(fiber, "run", stdFiberRun, -1); MODULE_ADD_FN(fiber, "resume", stdFiberResume, -1); diff --git a/src/pk_debug.c b/src/pk_debug.c index 3fb0088..f907227 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -141,10 +141,13 @@ void dumpFunctionCode(PKVM* vm, Function* func) { PRINT("]\n"); break; } - case OP_PUSH_MAP: NO_ARGS(); break; - case OP_LIST_APPEND: NO_ARGS(); break; - case OP_MAP_INSERT: NO_ARGS(); break; - case OP_INST_APPEND: NO_ARGS(); break; + case OP_PUSH_MAP: + case OP_PUSH_SELF: + case OP_LIST_APPEND: + case OP_MAP_INSERT: + case OP_INST_APPEND: + NO_ARGS(); + break; case OP_PUSH_LOCAL_0: case OP_PUSH_LOCAL_1: diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index c6eaf38..b1cae8a 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -40,6 +40,9 @@ OPCODE(PUSH_LIST, 2, 1) // Push a new map to construct from literal. OPCODE(PUSH_MAP, 0, 1) +// Push the self of the current method on the stack. +OPCODE(PUSH_SELF, 0, 1) + // Push a new instance to the stack. // param: 1 byte index. OPCODE(PUSH_INSTANCE, 1, 1) diff --git a/src/pk_value.c b/src/pk_value.c index 9a4c3d6..de892ec 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -263,6 +263,7 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { // Mark call frames. for (int i = 0; i < fiber->frame_count; i++) { markObject(vm, (Object*)&fiber->frames[i].closure->_super); + markValue(vm, fiber->frames[i].self); } vm->bytes_allocated += sizeof(CallFrame) * fiber->frame_capacity; @@ -501,38 +502,28 @@ Fiber* newFiber(PKVM* vm, Closure* closure) { return fiber; } -Class* newClass(PKVM* vm, Module* module, const char* name, uint32_t length, - int* cls_index, int* ctor_index) { +Class* newClass(PKVM* vm, const char* name, int length, + Module* module, const char* docstring, + int* cls_index) { Class* cls = ALLOCATE(vm, Class); varInitObject(&cls->_super, vm, OBJ_CLASS); vmPushTempRef(vm, &cls->_super); // class. - uint32_t _cls_index = moduleAddConstant(vm, module, VAR_OBJ(cls)); - if (cls_index) *cls_index = (int)_cls_index; - - pkUintBufferInit(&cls->field_names); - cls->owner = module; + cls->owner = NULL; cls->docstring = NULL; - cls->name = moduleAddString(module, vm, name, length, NULL); + cls->ctor = NULL; - // Since characters '@' and '$' are special in stringFormat, and they - // currently cannot be escaped (TODO), a string (char array) created - // for that character and passed as C string format. - char special[2] = { SPECIAL_NAME_CHAR, '\0' }; - String* ctor_name = stringFormat(vm, "$(Ctor:@)", special, cls->name); - - // Constructor. - vmPushTempRef(vm, &ctor_name->_super); // ctor_name. - { - Function* ctor_fn = newFunction(vm, ctor_name->data, ctor_name->length, - module, false, NULL, ctor_index); - vmPushTempRef(vm, &ctor_fn->_super); // ctor_fn. - cls->ctor = newClosure(vm, ctor_fn); - vmPopTempRef(vm); // ctor_fn. + // Builtin types doesn't belongs to a module. + if (module != NULL) { + cls->name = moduleAddString(module, vm, name, length, NULL); + int _cls_index = moduleAddConstant(vm, module, VAR_OBJ(cls)); + if (cls_index) *cls_index = _cls_index; + moduleAddGlobal(vm, module, name, length, VAR_OBJ(cls)); + } else { + cls->name = newStringLength(vm, name, (uint32_t)length); } - vmPopTempRef(vm); // ctor_name. vmPopTempRef(vm); // class. return cls; diff --git a/src/pk_value.h b/src/pk_value.h index ba2d24c..a316962 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -428,6 +428,7 @@ typedef struct { const uint8_t* ip; //< Pointer to the next instruction byte code. const Closure* closure; //< Closure of the frame. Var* rbp; //< Stack base pointer. (%rbp) + Var self; //< Self reference of the current method. } CallFrame; typedef enum { @@ -544,6 +545,12 @@ Range* newRange(PKVM* vm, double from, double to); Module* newModule(PKVM* vm); +Closure* newClosure(PKVM* vm, Function* fn); + +Upvalue* newUpvalue(PKVM* vm, Var* value); + +Fiber* newFiber(PKVM* vm, Closure* closure); + // FIXME: // The docstring should be allocated and stored in the module's constants // as a string if it's not a native function. (native function's docs are @@ -555,18 +562,12 @@ Function* newFunction(PKVM* vm, const char* name, int length, bool is_native, const char* docstring, int* fn_index); -Closure* newClosure(PKVM* vm, Function* fn); - -Upvalue* newUpvalue(PKVM* vm, Var* value); - -Fiber* newFiber(PKVM* vm, Closure* closure); - -// FIXME: -// Same fix has to applied as newFunction() (see above). -// -// Allocate new Class object and return Class* with name [name]. -Class* newClass(PKVM* vm, Module* scr, const char* name, uint32_t length, - int* cls_index, int* ctor_index); +// If the module is not NULL, the name and the class object will be added to +// the module's constant pool. The class will be added to the modules global +// as well. +Class* newClass(PKVM* vm, const char* name, int length, + Module* module, const char* docstring, + int* cls_index); // Allocate new instance with of the base [type]. Note that if [initialize] is // false, the field value buffer of the instance would be un initialized (ie. diff --git a/src/pk_vm.c b/src/pk_vm.c index f92cd52..bd5fd7e 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -555,6 +555,7 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) { frame->rbp = rbp; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; + frame->self = VAR_UNDEFINED; } static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { @@ -709,6 +710,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber_) { register const uint8_t* ip; register Var* rbp; //< Stack base pointer register. + register Var* self; //< Points to the self in the current call frame. register CallFrame* frame; //< Current call frame. register Module* module; //< Currently executing module. register Fiber* fiber = fiber_; @@ -770,6 +772,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber_) { frame = &fiber->frames[fiber->frame_count-1]; \ ip = frame->ip; \ rbp = frame->rbp; \ + self = &frame->self; \ module = frame->closure->fn->owner; \ } while (false) @@ -848,6 +851,12 @@ L_vm_main_loop: DISPATCH(); } + OPCODE(PUSH_SELF): + { + PUSH(*self); + DISPATCH(); + } + OPCODE(PUSH_INSTANCE): { uint8_t index = READ_SHORT(); diff --git a/tests/lang/class.pk b/tests/lang/class.pk index 93792b9..13e5e7c 100644 --- a/tests/lang/class.pk +++ b/tests/lang/class.pk @@ -1,36 +1,9 @@ -## TODO: Implement ctor with va arg to -## initialize, fields. -class _Vec - x = 0 - y = 0 -end +## Note that classes are being implemented and temproarly +## the classes cannot be compiled. -def Vec(x, y) - ret = _Vec() - ret.x = x; ret.y = y - return ret -end -def vecAdd(v1, v2) - return Vec(v1.x + v2.x, - v1.y + v2.y) -end -v1 = Vec(1, 2); assert(v1.x == 1 and v1.y == 2) -v2 = Vec(3, 4); assert(v2.x == 3 and v2.y == 4) -v3 = vecAdd(v1, v2) -assert(v3.x == 4 and v3.y == 6) - -class Test - fn = null - val = Vec(12, 32) -end - -test = Test() -test.fn = to_string -res = test.fn(test.val) -assert(res == "[_Vec: x=12, y=32]") diff --git a/tests/lang/fibers.pk b/tests/lang/fibers.pk index aacaa2e..0a6a79a 100644 --- a/tests/lang/fibers.pk +++ b/tests/lang/fibers.pk @@ -3,10 +3,10 @@ def f0() yield("yield value") end -import Fiber +import _Fiber -fiber = Fiber.new(f0) -yield_value = Fiber.run(fiber) +fiber = _Fiber.new(f0) +yield_value = _Fiber.run(fiber) assert(yield_value == "yield value") assert(!fiber.is_done) @@ -14,9 +14,9 @@ def f1() assert(yield("y") == "r") yield() end -fiber = Fiber.new(f1) -assert(Fiber.run(fiber) == "y") -Fiber.resume(fiber, "r") +fiber = _Fiber.new(f1) +assert(_Fiber.run(fiber) == "y") +_Fiber.resume(fiber, "r") assert(!fiber.is_done) def f2(p1, p2, p3) @@ -27,11 +27,11 @@ def f2(p1, p2, p3) return p1 + p2 * p3 end -fiber = Fiber.new(f2) -p3 = Fiber.run(fiber, 1, 2, 3); assert(p3 == 3) -p2 = Fiber.resume(fiber, 'r1'); assert(p2 == 2) -p1 = Fiber.resume(fiber, 'r2'); assert(p1 == 1) -pa = Fiber.resume(fiber, 'r3'); assert(pa == 7) +fiber = _Fiber.new(f2) +p3 = _Fiber.run(fiber, 1, 2, 3); assert(p3 == 3) +p2 = _Fiber.resume(fiber, 'r1'); assert(p2 == 2) +p1 = _Fiber.resume(fiber, 'r2'); assert(p1 == 1) +pa = _Fiber.resume(fiber, 'r3'); assert(pa == 7) assert(fiber.is_done) # If we got here, that means all test were passed. diff --git a/tests/lang/import.pk b/tests/lang/import.pk index d023488..1dbdd47 100644 --- a/tests/lang/import.pk +++ b/tests/lang/import.pk @@ -13,10 +13,6 @@ import "basics.pk" ## will import all import "controlflow.pk" as if_test from "functions.pk" import f1, f2 as fn2, f3 -import "class.pk" as class_ -assert(class_.Vec(42, 3.14).y == 3.14) -(vec = class_._Vec()).x = 'a'; assert(vec.x == 'a') - ## If it has a module name it'll bind to that name. import 'import/module.pk' assert(module_name.get_module_name() == 'module_name') From 4d46930d1b723599761425b6ed97e1fb5aa30358 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Wed, 20 Apr 2022 14:40:08 +0530 Subject: [PATCH 2/9] instance and class type cleaned this is a part of the class implementation. In this commit classes and instances type were completely cleaned and prepared for a new implementation. Native class registering mechanism were fully refactored and made it much simpler. --- cli/all.c | 4 +- cli/main.c | 8 +- cli/modules/modules.c | 91 --------- cli/modules/modules.h | 83 ++------ cli/modules/{std_file.c => std_io.c} | 83 +++++--- src/include/pocketlang.h | 270 ++++++++++++--------------- src/pk_core.c | 59 +++--- src/pk_debug.c | 18 +- src/pk_opcodes.h | 8 - src/pk_value.c | 243 ++++-------------------- src/pk_value.h | 30 ++- src/pk_vm.c | 31 --- 12 files changed, 279 insertions(+), 649 deletions(-) delete mode 100644 cli/modules/modules.c rename cli/modules/{std_file.c => std_io.c} (59%) diff --git a/cli/all.c b/cli/all.c index 22e0d50..1b8cae7 100644 --- a/cli/all.c +++ b/cli/all.c @@ -13,9 +13,7 @@ /* STD MODULE SOURCES */ /*****************************************************************************/ -#include "modules/modules.c" - -#include "modules/std_file.c" +#include "modules/std_io.c" #include "modules/std_path.c" /*****************************************************************************/ diff --git a/cli/main.c b/cli/main.c index 1c69f74..3c317eb 100644 --- a/cli/main.c +++ b/cli/main.c @@ -129,11 +129,6 @@ static PKVM* intializePocketVM() { config.write_fn = writeFunction; config.read_fn = readFunction; - 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; @@ -193,7 +188,8 @@ int main(int argc, const char** argv) { user_data.repl_mode = false; pkSetUserData(vm, &user_data); - registerModules(vm); + registerModulePath(vm); + //REGISTER_ALL_MODULES(vm); PkCompileOptions options = pkNewCompilerOptions(); options.debug = debug; diff --git a/cli/modules/modules.c b/cli/modules/modules.c deleted file mode 100644 index 9ff605b..0000000 --- a/cli/modules/modules.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2020-2022 Thakee Nathees - * Copyright (c) 2021-2022 Pocketlang Contributors - * Distributed Under The MIT License - */ - -#include "modules.h" - -// Note: Everything here is for testing the native API, and will have to -// refactor everything. - -// Allocate a new module object of type [Ty]. -#define NEW_OBJ(Ty) (Ty*)malloc(sizeof(Ty)) - -// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj -// callback. -#define FREE_OBJ(ptr) free(ptr) - -/*****************************************************************************/ -/* MODULE FUNCTIONS DECLARATION */ -/*****************************************************************************/ - -void fileGetAttrib(PKVM* vm, File* file, const char* attrib); -bool fileSetAttrib(PKVM* vm, File* file, const char* attrib); -void fileClean(PKVM* vm, File* file); - -void registerModuleFile(PKVM* vm); -void registerModulePath(PKVM* vm); - -/*****************************************************************************/ -/* MODULE PUBLIC FUNCTIONS */ -/*****************************************************************************/ - -void initObj(Obj* obj, ObjType type) { - obj->type = type; -} - -void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { - Obj* obj = (Obj*)instance; - ASSERT(obj->type == (ObjType)id, OOPS); - - switch (obj->type) { - case OBJ_FILE: - fileGetAttrib(vm, (File*)obj, attrib.string); - return; - } - STATIC_ASSERT(_OBJ_MAX_ == 2); -} - -bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { - Obj* obj = (Obj*)instance; - ASSERT(obj->type == (ObjType)id, OOPS); - - switch (obj->type) { - case OBJ_FILE: - return fileSetAttrib(vm, (File*)obj, attrib.string); - } - STATIC_ASSERT(_OBJ_MAX_ == 2); - - return false; -} - -void freeObj(PKVM* vm, void* instance, uint32_t id) { - Obj* obj = (Obj*)instance; - ASSERT(obj->type == (ObjType)id, OOPS); - - switch (obj->type) { - case OBJ_FILE: - fileClean(vm, (File*)obj); - } - STATIC_ASSERT(_OBJ_MAX_ == 2); - - FREE_OBJ(obj); -} - -const char* getObjName(uint32_t id) { - switch ((ObjType)id) { - case OBJ_FILE: return "File"; - } - STATIC_ASSERT(_OBJ_MAX_ == 2); - return NULL; -} - -/*****************************************************************************/ -/* REGISTER MODULES */ -/*****************************************************************************/ - -void registerModules(PKVM* vm) { - registerModuleFile(vm); - registerModulePath(vm); -} diff --git a/cli/modules/modules.h b/cli/modules/modules.h index 842feb2..d81854b 100644 --- a/cli/modules/modules.h +++ b/cli/modules/modules.h @@ -14,73 +14,26 @@ #include #include -/*****************************************************************************/ -/* MODULE OBJECTS */ -/*****************************************************************************/ - -// Str | If already exists | If does not exist | -// -----+-------------------+-------------------| -// 'r' | read from start | failure to open | -// 'w' | destroy contents | create new | -// 'a' | write to end | create new | -// 'r+' | read from start | error | -// 'w+' | destroy contents | create new | -// 'a+' | write to end | create new | -typedef enum { - FMODE_READ = (1 << 0), - FMODE_WRITE = (1 << 1), - FMODE_APPEND = (1 << 2), - _FMODE_EXT = (1 << 3), - FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ), - FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE), - FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND), -} FileAccessMode; - -typedef enum { - OBJ_FILE = 1, - - _OBJ_MAX_ -} ObjType; - -typedef struct { - ObjType type; -} Obj; - -typedef struct { - Obj _super; - FILE* fp; // C file poinnter. - FileAccessMode mode; // Access mode of the file. - bool closed; // True if the file isn't closed yet. -} File; - -/*****************************************************************************/ -/* MODULE PUBLIC FUNCTIONS */ -/*****************************************************************************/ - -// Initialize the native module object with it's default values. -void initObj(Obj* obj, ObjType type); - -// A function callback called by pocket VM to get attribute of a native -// instance. The value of the attributes will be returned with pkReturn...() -// functions and if the attribute doesn't exists on the instance we're -// shouldn't return anything, PKVM will know it and set error (or use some -// common attributes like "as_string", "as_repr", etc). -void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib); - -// A function callback called by pocket VM to set attribute of a native -// instance. -bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib); - -// The free callback of the object, that'll called by pocketlang when a -// pocketlang native instance garbage collected. -void freeObj(PKVM* vm, void* instance, uint32_t id); - -// The native instance get_name callback used to get the name of a native -// instance from pocketlang. Here the id we're using is the ObjType enum. -const char* getObjName(uint32_t id); +void registerModuleIO(PKVM* vm); +void registerModulePath(PKVM* vm); // Registers all the cli modules. -void registerModules(PKVM* vm); +#define REGISTER_ALL_MODULES(vm) \ + do { \ + registerModuleIO(vm); \ + registerModulePath(vm); \ + } while (false) + +/*****************************************************************************/ +/* MODULES INTERNAL */ +/*****************************************************************************/ + +// Allocate a new module object of type [Ty]. +#define NEW_OBJ(Ty) (Ty*)malloc(sizeof(Ty)) + +// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj +// callback. +#define FREE_OBJ(ptr) free(ptr) /*****************************************************************************/ /* SHARED FUNCTIONS */ diff --git a/cli/modules/std_file.c b/cli/modules/std_io.c similarity index 59% rename from cli/modules/std_file.c rename to cli/modules/std_io.c index 7401177..aa944a9 100644 --- a/cli/modules/std_file.c +++ b/cli/modules/std_io.c @@ -6,6 +6,31 @@ #include "modules.h" + // Str | If already exists | If does not exist | + // -----+-------------------+-------------------| + // 'r' | read from start | failure to open | + // 'w' | destroy contents | create new | + // 'a' | write to end | create new | + // 'r+' | read from start | error | + // 'w+' | destroy contents | create new | + // 'a+' | write to end | create new | +typedef enum { + FMODE_NONE = 0, + FMODE_READ = (1 << 0), + FMODE_WRITE = (1 << 1), + FMODE_APPEND = (1 << 2), + _FMODE_EXT = (1 << 3), + FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ), + FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE), + FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND), +} FileAccessMode; + +typedef struct { + FILE* fp; // C file poinnter. + FileAccessMode mode; // Access mode of the file. + bool closed; // True if the file isn't closed yet. +} File; + /*****************************************************************************/ /* FILE OBJECT OPERATORS */ /*****************************************************************************/ @@ -62,25 +87,29 @@ static void _fileOpen(PKVM* vm) { } while (false); } + // This TODO is just a blockade from running the bellow code, complete the + // native interface and test before removing it. + TODO; + FILE* fp = fopen(path, mode_str); if (fp != NULL) { - File* file = NEW_OBJ(File); - initObj(&file->_super, OBJ_FILE); - file->fp = fp; - file->mode = mode; - file->closed = false; - - pkReturnInstNative(vm, (void*)file, OBJ_FILE); + File* self = (File*)pkGetSelf(vm); + self->fp = fp; + self->mode = mode; + self->closed = false; } else { - pkReturnNull(vm); + pkSetRuntimeError(vm, "Error opening the file."); } } static void _fileRead(PKVM* vm) { - File* file; - if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; + // This TODO is just a blockade from running the bellow code, complete the + // native interface and test before removing it. + TODO; + + File* file = (File*)pkGetSelf(vm); if (file->closed) { pkSetRuntimeError(vm, "Cannot read from a closed file."); @@ -99,10 +128,13 @@ static void _fileRead(PKVM* vm) { } static void _fileWrite(PKVM* vm) { - File* file; + // This TODO is just a blockade from running the bellow code, complete the + // native interface and test before removing it. + TODO; + + File* file = (File*)pkGetSelf(vm); const char* text; uint32_t length; - if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; - if (!pkGetArgString(vm, 2, &text, &length)) return; + if (!pkGetArgString(vm, 1, &text, &length)) return; if (file->closed) { pkSetRuntimeError(vm, "Cannot write to a closed file."); @@ -118,8 +150,11 @@ static void _fileWrite(PKVM* vm) { } static void _fileClose(PKVM* vm) { - File* file; - if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; + // This TODO is just a blockade from running the bellow code, complete the + // native interface and test before removing it. + TODO; + + File* file = (File*)pkGetSelf(vm); if (file->closed) { pkSetRuntimeError(vm, "File already closed."); @@ -133,14 +168,16 @@ static void _fileClose(PKVM* vm) { file->closed = true; } -void registerModuleFile(PKVM* vm) { - PkHandle* file = pkNewModule(vm, "File"); +void registerModuleIO(PKVM* vm) { + PkHandle* io = pkNewModule(vm, "io"); - pkModuleAddFunction(vm, file, "open", _fileOpen, -1); - pkModuleAddFunction(vm, file, "read", _fileRead, 1); - pkModuleAddFunction(vm, file, "write", _fileWrite, 2); - pkModuleAddFunction(vm, file, "close", _fileClose, 1); + PkHandle* cls_file = pkNewClass(vm, io, "File"); + pkClassAddMethod(vm, cls_file, "open", _fileOpen, -1); + pkClassAddMethod(vm, cls_file, "read", _fileRead, 0); + pkClassAddMethod(vm, cls_file, "write", _fileWrite, 1); + pkClassAddMethod(vm, cls_file, "close", _fileClose, 0); + pkReleaseHandle(vm, cls_file); - pkRegisterModule(vm, file); - pkReleaseHandle(vm, file); + pkRegisterModule(vm, io); + pkReleaseHandle(vm, io); } diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index da92a03..58b9f46 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -55,8 +55,9 @@ extern "C" { #define PK_PUBLIC #endif + /*****************************************************************************/ -/* POCKETLANG TYPES */ +/* POCKETLANG TYPEDEFS & CALLBACKS */ /*****************************************************************************/ // PocketLang Virtual Machine. It'll contain the state of the execution, stack, @@ -74,57 +75,13 @@ typedef struct PkHandle PkHandle; // alive use `pkNewHandle()`. typedef void* PkVar; -// Type enum of the pocketlang's first class types. Note that Object isn't -// instanciable (as of now) but they're considered first calss. -typedef enum { - PK_OBJECT = 0, - - PK_NULL, - PK_BOOL, - PK_NUMBER, - PK_STRING, - PK_LIST, - PK_MAP, - PK_RANGE, - PK_MODULE, - PK_CLOSURE, - PK_FIBER, - PK_CLASS, - PK_INSTANCE, -} PkVarType; - +typedef enum PkVarType PkVarType; +typedef enum PkErrorType PkErrorType; +typedef enum PkResult PkResult; typedef struct PkStringPtr PkStringPtr; typedef struct PkConfiguration PkConfiguration; typedef struct PkCompileOptions PkCompileOptions; -// Type of the error message that pocketlang will provide with the pkErrorFn -// callback. -typedef enum { - PK_ERROR_COMPILE = 0, // Compile time errors. - PK_ERROR_RUNTIME, // Runtime error message. - PK_ERROR_STACKTRACE, // One entry of a runtime error stack. -} PkErrorType; - -// Result that pocketlang will return after a compilation or running a script -// or a function or evaluating an expression. -typedef enum { - PK_RESULT_SUCCESS = 0, // Successfully finished the execution. - - // Unexpected EOF while compiling the source. This is another compile time - // error that will ONLY be returned if we're compiling with the REPL mode set - // in the compile options. We need this specific error to indicate the host - // application to add another line to the last input. If REPL is not enabled, - // this will be PK_RESULT_COMPILE_ERROR. - PK_RESULT_UNEXPECTED_EOF, - - PK_RESULT_COMPILE_ERROR, // Compilation failed. - PK_RESULT_RUNTIME_ERROR, // An error occurred at runtime. -} PkResult; - -/*****************************************************************************/ -/* POCKETLANG FUNCTION POINTERS & CALLBACKS */ -/*****************************************************************************/ - // C function pointer which is callable from pocketLang by native module // functions. typedef void (*pkNativeFn)(PKVM* vm); @@ -155,35 +112,6 @@ typedef void (*pkWriteFn) (PKVM* vm, const char* text); // contain a line ending (\n or \r\n). typedef PkStringPtr (*pkReadFn) (PKVM* vm); -// A function callback, that'll be called when a native instance (wrapper) is -// freed by by the garbage collector, to indicate that pocketlang is done with -// the native instance. -typedef void (*pkInstFreeFn) (PKVM* vm, void* instance, uint32_t id); - -// A function callback to get the type name of the native instance from -// pocketlang, using it's [id]. The returned string won't be copied by -// pocketlang so it's expected to be alived since the instance is alive and -// recomended to return a C literal string. -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. 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, uint32_t id, - PkStringPtr attrib); - -// 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, uint32_t id, - PkStringPtr attrib); - // A function callback symbol for clean/free the pkStringResult. typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result); @@ -199,6 +127,96 @@ typedef PkStringPtr (*pkResolvePathFn) (PKVM* vm, const char* from, // to indicate if it's failed to load the script. typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path); +/*****************************************************************************/ +/* POCKETLANG TYPES */ +/*****************************************************************************/ + +// Type enum of the pocketlang's first class types. Note that Object isn't +// instanciable (as of now) but they're considered first calss. +enum PkVarType { + PK_OBJECT = 0, + + PK_NULL, + PK_BOOL, + PK_NUMBER, + PK_STRING, + PK_LIST, + PK_MAP, + PK_RANGE, + PK_MODULE, + PK_CLOSURE, + PK_FIBER, + PK_CLASS, + PK_INSTANCE, +}; + +// Type of the error message that pocketlang will provide with the pkErrorFn +// callback. +enum PkErrorType { + PK_ERROR_COMPILE = 0, // Compile time errors. + PK_ERROR_RUNTIME, // Runtime error message. + PK_ERROR_STACKTRACE, // One entry of a runtime error stack. +}; + +// Result that pocketlang will return after a compilation or running a script +// or a function or evaluating an expression. +enum PkResult { + PK_RESULT_SUCCESS = 0, // Successfully finished the execution. + + // Unexpected EOF while compiling the source. This is another compile time + // error that will ONLY be returned if we're compiling with the REPL mode set + // in the compile options. We need this specific error to indicate the host + // application to add another line to the last input. If REPL is not enabled, + // this will be PK_RESULT_COMPILE_ERROR. + PK_RESULT_UNEXPECTED_EOF, + + PK_RESULT_COMPILE_ERROR, // Compilation failed. + PK_RESULT_RUNTIME_ERROR, // An error occurred at runtime. +}; + +// A string pointer wrapper to pass c string between host application and +// pocket VM. With a on_done() callback to clean it when the pocket VM is done +// with the string. +struct PkStringPtr { + const char* string; //< The string result. + pkResultDoneFn on_done; //< Called once vm done with the string. + void* user_data; //< User related data. + + // These values are provided by the pocket VM to the host application, you're + // not expected to set this when provideing string to the pocket VM. + uint32_t length; //< Length of the string. + uint32_t hash; //< Its 32 bit FNV-1a hash. +}; + +struct PkConfiguration { + + // The callback used to allocate, reallocate, and free. If the function + // pointer is NULL it defaults to the VM's realloc(), free() wrappers. + pkReallocFn realloc_fn; + + pkErrorFn error_fn; + pkWriteFn write_fn; + pkReadFn read_fn; + + pkResolvePathFn resolve_path_fn; + pkLoadScriptFn load_script_fn; + + // User defined data associated with VM. + void* user_data; +}; + +// The options to configure the compilation provided by the command line +// arguments (or other ways the host application provides). +struct PkCompileOptions { + + // Compile debug version of the source. + bool debug; + + // Set to true if compiling in REPL mode, This will print repr version of + // each evaluated non-null values. + bool repl_mode; +}; + /*****************************************************************************/ /* POCKETLANG PUBLIC API */ /*****************************************************************************/ @@ -250,7 +268,7 @@ PK_PUBLIC PkHandle* pkModuleGetGlobal(PKVM* vm, PkHandle* module, const char* name); // Add a native function to the given module. If [arity] is -1 that means -// The function has variadic parameters and use pkGetArgc() to get the argc. +// the function has variadic parameters and use pkGetArgc() to get the argc. // Note that the function will be added as a global variable of the module, // to retrieve the function use pkModuleGetGlobal(). PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module, @@ -261,6 +279,26 @@ PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module, // it's statements are wrapped around an implicit main function. PK_PUBLIC PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module); +// Add a new module named [name] to the [vm]. Note that the module shouldn't +// already existed, otherwise an assertion will fail to indicate that. +PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name); + +// Register the module to the PKVM's modules map, once after it can be +// imported in other modules. +PK_PUBLIC void pkRegisterModule(PKVM* vm, PkHandle* module); + +// Create a new class on the [module] with the [name] and return it. +PK_PUBLIC PkHandle* pkNewClass(PKVM* vm, PkHandle* module, const char* name); + +// Add a native method to the given class. If the [arity] is -1 that means +// the method has variadic parameters and use pkGetArgc() to get the argc. +PK_PUBLIC void pkClassAddMethod(PKVM* vm, PkHandle* cls, + const char* name, + pkNativeFn fptr, int arity); + +// Create and return a new fiber around the function [fn]. +PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn); + // Compile the [module] with the provided [source]. Set the compiler options // with the the [options] argument or set to NULL for default options. PK_PUBLIC PkResult pkCompileModule(PKVM* vm, PkHandle* module, @@ -289,59 +327,6 @@ PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber, // yielded or returned value use the pkFiberGetReturnValue() function. PK_PUBLIC PkResult pkResumeFiber(PKVM* vm, PkHandle* fiber, PkVar value); -/*****************************************************************************/ -/* POCKETLANG PUBLIC TYPE DEFINES */ -/*****************************************************************************/ - -// A string pointer wrapper to pass c string between host application and -// pocket VM. With a on_done() callback to clean it when the pocket VM is done -// with the string. -struct PkStringPtr { - const char* string; //< The string result. - pkResultDoneFn on_done; //< Called once vm done with the string. - void* user_data; //< User related data. - - // These values are provided by the pocket VM to the host application, you're - // not expected to set this when provideing string to the pocket VM. - uint32_t length; //< Length of the string. - uint32_t hash; //< Its 32 bit FNV-1a hash. -}; - -struct PkConfiguration { - - // The callback used to allocate, reallocate, and free. If the function - // pointer is NULL it defaults to the VM's realloc(), free() wrappers. - pkReallocFn realloc_fn; - - pkErrorFn error_fn; - pkWriteFn write_fn; - pkReadFn read_fn; - - pkInstFreeFn inst_free_fn; - pkInstNameFn inst_name_fn; - pkInstGetAttribFn inst_get_attrib_fn; - pkInstSetAttribFn inst_set_attrib_fn; - - pkResolvePathFn resolve_path_fn; - pkLoadScriptFn load_script_fn; - - // User defined data associated with VM. - void* user_data; -}; - -// The options to configure the compilation provided by the command line -// arguments (or other ways the host application provides). -struct PkCompileOptions { - - // Compile debug version of the source. - bool debug; - - // Set to true if compiling in REPL mode, This will print repr version of - // each evaluated non-null values. - bool repl_mode; - -}; - /*****************************************************************************/ /* NATIVE FUNCTION API */ /*****************************************************************************/ @@ -352,6 +337,9 @@ PK_PUBLIC void pkSetRuntimeError(PKVM* vm, const char* message); // TODO: Set a runtime error to VM, with the formated string. //PK_PUBLIC void pkSetRuntimeErrorFmt(PKVM* vm, const char* fmt, ...); +// Returns native [self] of the current method as a void*. +PK_PUBLIC void* pkGetSelf(const PKVM* vm); + // Return the type of the [value] this will help to get the type of the // variable that was extracted from pkGetArg() earlier. PK_PUBLIC PkVarType pkGetValueType(const PkVar value); @@ -383,7 +371,6 @@ PK_PUBLIC bool pkGetArgBool(PKVM* vm, int arg, bool* value); PK_PUBLIC bool pkGetArgNumber(PKVM* vm, int arg, double* value); PK_PUBLIC bool pkGetArgString(PKVM* vm, int arg, const char** value, uint32_t* length); -PK_PUBLIC bool pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value); PK_PUBLIC bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value); // The functions follow are used to set the return value of the current native @@ -397,8 +384,6 @@ PK_PUBLIC void pkReturnStringLength(PKVM* vm, const char* value, size_t len); PK_PUBLIC void pkReturnValue(PKVM* vm, PkVar value); PK_PUBLIC void pkReturnHandle(PKVM* vm, PkHandle* handle); -PK_PUBLIC void pkReturnInstNative(PKVM* vm, void* data, uint32_t id); - // Returns the cstring pointer of the given string. Make sure if the [value] is // a string before calling this function, otherwise it'll fail an assertion. PK_PUBLIC const char* pkStringGetData(const PkVar value); @@ -424,23 +409,6 @@ PK_PUBLIC PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len); PK_PUBLIC PkHandle* pkNewList(PKVM* vm); PK_PUBLIC PkHandle* pkNewMap(PKVM* vm); -// Add a new module named [name] to the [vm]. Note that the module shouldn't -// already existed, otherwise an assertion will fail to indicate that. -PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name); - -// Register the module to the PKVM's modules map, once after it can be -// imported in other modules. -PK_PUBLIC void pkRegisterModule(PKVM* vm, PkHandle* module); - -// Create and return a new fiber around the function [fn]. -PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn); - -// Create and return a native instance around the [data]. The [id] is the -// unique id of the instance, this would be used to check if two instances are -// equal and used to get the name of the instance using NativeTypeNameFn -// callback. -PK_PUBLIC PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id); - // TODO: Create a primitive (non garbage collected) variable buffer (or a // fixed size array) to store them and make the handle points to the variable // in that buffer, this will prevent us from invoking an allocation call for diff --git a/src/pk_core.c b/src/pk_core.c index 82b07ad..5d9ee61 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -69,6 +69,34 @@ void pkRegisterModule(PKVM* vm, PkHandle* module) { vmRegisterModule(vm, module_, module_->name); } +PkHandle* pkNewClass(PKVM* vm, PkHandle* module, const char* name) { + CHECK_NULL(module); + CHECK_NULL(name); + CHECK_TYPE(module, OBJ_MODULE); + + Class* class_ = newClass(vm, name, (int)strlen(name), + (Module*)AS_OBJ(module->value), NULL, NULL); + return vmNewHandle(vm, VAR_OBJ(class_)); +} + +void pkClassAddMethod(PKVM* vm, PkHandle* cls, + const char* name, + pkNativeFn fptr, int arity) { + CHECK_NULL(cls); + CHECK_NULL(fptr); + CHECK_TYPE(cls, OBJ_MODULE); + + TODO; +} + +void* pkGetSelf(const PKVM* vm) { + Var self = vm->fiber->frames[vm->fiber->frame_count - 1].self; + ASSERT(IS_OBJ_TYPE(self, OBJ_INST), OOPS); + Instance* inst = (Instance*)AS_OBJ(self); + ASSERT(inst->native != NULL, OOPS); + return inst->native; +} + void pkModuleAddGlobal(PKVM* vm, PkHandle* module, const char* name, PkHandle* value) { CHECK_TYPE(module, OBJ_MODULE); @@ -230,33 +258,6 @@ bool pkGetArgString(PKVM* vm, int arg, const char** value, uint32_t* length) { return true; } -bool pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value) { - CHECK_GET_ARG_API_ERRORS(); - - Var val = ARG(arg); - bool is_native_instance = false; - - if (IS_OBJ_TYPE(val, OBJ_INST)) { - Instance* inst = ((Instance*)AS_OBJ(val)); - if (inst->is_native && inst->native_id == id) { - *value = inst->native; - is_native_instance = true; - } - } - - if (!is_native_instance) { - const char* ty_name = "$(?)"; - if (vm->config.inst_name_fn != NULL) { - ty_name = vm->config.inst_name_fn(id); - } - - ERR_INVALID_ARG_TYPE(ty_name); - return false; - } - - return true; -} - bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) { CHECK_GET_ARG_API_ERRORS(); @@ -300,10 +301,6 @@ void pkReturnHandle(PKVM* vm, PkHandle* handle) { RET(handle->value); } -void pkReturnInstNative(PKVM* vm, void* data, uint32_t id) { - RET(VAR_OBJ(newInstanceNative(vm, data, id))); -} - const char* pkStringGetData(const PkVar value) { const Var str = (*(const Var*)value); ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string."); diff --git a/src/pk_debug.c b/src/pk_debug.c index f907227..a86aff6 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -126,26 +126,14 @@ void dumpFunctionCode(PKVM* vm, Function* func) { NO_ARGS(); break; - case OP_PUSH_LIST: SHORT_ARG(); break; - case OP_PUSH_INSTANCE: - { - int cls_index = READ_SHORT(); - ASSERT_INDEX((uint32_t)cls_index, func->owner->constants.count); - Var constant = func->owner->constants.data[cls_index]; - ASSERT(IS_OBJ_TYPE(constant, OBJ_CLASS), OOPS); - - // Prints: %5d [Class:%s]\n - PRINT_INT(cls_index); - PRINT(" [Class:"); - PRINT(func->owner->name->data); - PRINT("]\n"); + case OP_PUSH_LIST: + SHORT_ARG(); break; - } + case OP_PUSH_MAP: case OP_PUSH_SELF: case OP_LIST_APPEND: case OP_MAP_INSERT: - case OP_INST_APPEND: NO_ARGS(); break; diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index b1cae8a..1a7a561 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -43,10 +43,6 @@ OPCODE(PUSH_MAP, 0, 1) // Push the self of the current method on the stack. OPCODE(PUSH_SELF, 0, 1) -// Push a new instance to the stack. -// param: 1 byte index. -OPCODE(PUSH_INSTANCE, 1, 1) - // Pop the value on the stack the next stack top would be a list. Append the // value to the list. Used in literal array construction. OPCODE(LIST_APPEND, 0, -1) @@ -55,10 +51,6 @@ OPCODE(LIST_APPEND, 0, -1) // Insert the key value pairs to the map. Used in literal map construction. OPCODE(MAP_INSERT, 0, -2) -// Pop the value on the stack, the next stack top would be an instance. Append -// the value to the instance. Used in instance construction. -OPCODE(INST_APPEND, 0, -1) - // Push stack local on top of the stack. Locals at 0 to 8 marked explicitly // since it's performance critical. // params: PUSH_LOCAL_N -> 1 byte count value. diff --git a/src/pk_value.c b/src/pk_value.c index de892ec..07bcfad 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -75,14 +75,6 @@ PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) { return handle; } -PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id) { - Instance* inst = newInstanceNative(vm, data, id); - vmPushTempRef(vm, &inst->_super); // inst - PkHandle* handle = vmNewHandle(vm, VAR_OBJ(inst)); - vmPopTempRef(vm); // inst - return handle; -} - /*****************************************************************************/ /* VAR INTERNALS */ /*****************************************************************************/ @@ -279,17 +271,14 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { markObject(vm, &cls->owner->_super); markObject(vm, &cls->ctor->_super); markObject(vm, &cls->name->_super); - vm->bytes_allocated += sizeof(uint32_t) * cls->field_names.capacity; } break; case OBJ_INST: { Instance* inst = (Instance*)obj; - if (!inst->is_native) { - Inst* ins = inst->ins; - vm->bytes_allocated += sizeof(Inst); - vm->bytes_allocated += sizeof(Var*) * ins->fields.capacity; - } + markObject(vm, &inst->attribs._super); + markObject(vm, &inst->cls->_super); + vm->bytes_allocated += sizeof(Instance); } break; } } @@ -529,43 +518,11 @@ Class* newClass(PKVM* vm, const char* name, int length, return cls; } -Instance* newInstance(PKVM* vm, Class* cls, bool initialize) { - +Instance* newInstance(PKVM* vm, Class* cls) { Instance* inst = ALLOCATE(vm, Instance); varInitObject(&inst->_super, vm, OBJ_INST); - - vmPushTempRef(vm, &inst->_super); // inst. - - inst->ty_name = cls->name->data; - inst->is_native = false; - - Inst* ins = ALLOCATE(vm, Inst); - inst->ins = ins; - ins->type = cls; - pkVarBufferInit(&ins->fields); - - if (initialize && cls->field_names.count != 0) { - pkVarBufferFill(&ins->fields, vm, VAR_NULL, cls->field_names.count); - } - - vmPopTempRef(vm); // inst. - - return inst; -} - -Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id) { - Instance* inst = ALLOCATE(vm, Instance); - varInitObject(&inst->_super, vm, OBJ_INST); - inst->is_native = true; - inst->native_id = id; - - if (vm->config.inst_name_fn != NULL) { - inst->ty_name = vm->config.inst_name_fn(id); - } else { - inst->ty_name = "$(?)"; - } - - inst->native = data; + inst->cls = cls; + inst->native = NULL; return inst; } @@ -1058,27 +1015,14 @@ void freeObject(PKVM* vm, Object* self) { case OBJ_CLASS: { Class* cls = (Class*)self; - pkUintBufferClear(&cls->field_names, vm); } break; - case OBJ_INST: - { + case OBJ_INST: { Instance* inst = (Instance*)self; - - if (inst->is_native) { - if (vm->config.inst_free_fn != NULL) { - // TODO: Allow user to set error when freeing the object. - vm->config.inst_free_fn(vm, inst->native, inst->native_id); - } - - } else { - Inst* ins = inst->ins; - pkVarBufferClear(&ins->fields, vm); - DEALLOCATE(vm, ins); + if (inst->native) { + TODO; // Call native clean function. } - - break; - } + } break; } DEALLOCATE(vm, self); @@ -1185,129 +1129,35 @@ void moduleAddMain(PKVM* vm, Module* module) { } bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value) { - ASSERT(inst != NULL, OOPS); - ASSERT(attrib != NULL, OOPS); - ASSERT(value != NULL, OOPS); + ASSERT((inst != NULL) && (attrib != NULL) && (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, inst->native_id, attr); - vm->fiber->ret = temp; - - if (IS_UNDEF(val)) { - - // FIXME: add a list of attribute overrides. - if ((CHECK_HASH("as_string", 0xbdef4147) == attrib->hash) && - IS_CSTR_EQ(attrib, "as_string", 9)) { - *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* cls = inst->ins->type; - for (uint32_t i = 0; i < cls->field_names.count; i++) { - ASSERT_INDEX(i, cls->field_names.count); - String* f_name = moduleGetStringAt(cls->owner, cls->field_names.data[i]); - ASSERT(f_name != NULL, OOPS); - 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; + if (inst->native != NULL) { } + TODO; - UNREACHABLE(); - return false; + Var value_ = mapGet(&inst->attribs, VAR_OBJ(attrib)); + if (IS_UNDEF(value_)) return false; + + *value = value_; + return true; } bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value) { + ASSERT((inst != NULL) && (attrib != NULL), OOPS); - if (inst->is_native) { + if (inst->native != NULL) { + // Try setting the attribute from the native interface, and if success, we + // should return. otherwise the code will "fall through" and set on it's + // dynamic attributes map. + TODO; - if (vm->config.inst_set_attrib_fn) { - // Temproarly change the fiber's "return address" to points to the - // below var 'attrib_ptr' so that the users can use 'pkGetArg...()' - // function to validate and get the attribute (users should use 0 as the - // index of the argument since it's at the return address and we cannot - // ensure fiber->ret[1] will be in bounds). - 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, - inst->native_id, 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); - String* f_name = moduleGetStringAt(ty->owner, ty->field_names.data[i]); - ASSERT(f_name != NULL, OOPS); - 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; + // FIXME: + // Only return true if attribute have been set. + return true; } - UNREACHABLE(); - return false; + mapSet(vm, &inst->attribs, VAR_OBJ(attrib), value); + return true; } /*****************************************************************************/ @@ -1693,35 +1543,16 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff, { const Instance* inst = (const Instance*)obj; pkByteBufferWrite(buff, vm, '['); - pkByteBufferAddString(buff, vm, inst->ty_name, - (uint32_t)strlen(inst->ty_name)); + pkByteBufferAddString(buff, vm, inst->cls->name->data, + inst->cls->name->length); pkByteBufferWrite(buff, vm, ':'); - if (!inst->is_native) { - const Class* cls = inst->ins->type; - const Inst* ins = inst->ins; - ASSERT(ins->fields.count == cls->field_names.count, OOPS); - - for (uint32_t i = 0; i < cls->field_names.count; i++) { - if (i != 0) pkByteBufferWrite(buff, vm, ','); - - pkByteBufferWrite(buff, vm, ' '); - String* f_name = moduleGetStringAt(cls->owner, - cls->field_names.data[i]); - pkByteBufferAddString(buff, vm, f_name->data, f_name->length); - pkByteBufferWrite(buff, vm, '='); - _toStringInternal(vm, ins->fields.data[i], buff, outer, repr); - } - } else { - - char buff_addr[STR_HEX_BUFF_SIZE]; - char* ptr = (char*)buff_addr; - (*ptr++) = '0'; (*ptr++) = 'x'; - const int len = snprintf(ptr, sizeof(buff_addr) - 2, - "%08x", (unsigned int)(uintptr_t)inst->native); - pkByteBufferAddString(buff, vm, buff_addr, (uint32_t)len); - } - + char buff_addr[STR_HEX_BUFF_SIZE]; + char* ptr = (char*)buff_addr; + (*ptr++) = '0'; (*ptr++) = 'x'; + const int len = snprintf(ptr, sizeof(buff_addr) - 2, + "%08x", (unsigned int)(uintptr_t)inst); + pkByteBufferAddString(buff, vm, buff_addr, (uint32_t)len); pkByteBufferWrite(buff, vm, ']'); return; } diff --git a/src/pk_value.h b/src/pk_value.h index a316962..b8e80c2 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -491,8 +491,6 @@ struct Class { const char* docstring; Closure* ctor; //< The constructor function. - pkUintBuffer field_names; //< Buffer of field names. - // TODO: ordered names buffer for binary search. }; typedef struct { @@ -503,15 +501,17 @@ typedef struct { struct Instance { Object _super; - const char* ty_name; //< Name of the type it belongs to. + Class* cls; //< Class of the instance. - bool is_native; //< True if it's a native type instance. - uint32_t native_id; //< Unique ID of this native instance. + // If the instance is native, the [native] pointer points to the user data + // (generally a heap allocated struct of that type) that contains it's + // attributes. We'll use it to access an attribute first with setters and + // getters and if the attribute not exists we'll continue search in the + // bellow attribs map. + void* native; - union { - void* native; //< C struct pointer. // TODO: - Inst* ins; //< Module instance pointer. - }; + // Dynamic attributes of an instance. + Map attribs; }; /*****************************************************************************/ @@ -569,16 +569,8 @@ Class* newClass(PKVM* vm, const char* name, int length, Module* module, const char* docstring, int* cls_index); -// Allocate new instance with of the base [type]. Note that if [initialize] is -// false, the field value buffer of the instance would be un initialized (ie. -// the buffer count = 0). Otherwise they'll be set to VAR_NULL. -Instance* newInstance(PKVM* vm, Class* cls, bool initialize); - -// Allocate new native instance and with [data] as the native type handle and -// return Instance*. The [id] is the unique id of the instance, this would be -// used to check if two instances are equal and used to get the name of the -// instance using NativeTypeNameFn callback. -Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id); +// Allocate new instance with of the base [type]. +Instance* newInstance(PKVM* vm, Class* cls); /*****************************************************************************/ /* METHODS */ diff --git a/src/pk_vm.c b/src/pk_vm.c index bd5fd7e..4de2829 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -31,11 +31,6 @@ PkConfiguration pkNewConfiguration(void) { config.write_fn = NULL; config.read_fn = NULL; - config.inst_free_fn = NULL; - config.inst_name_fn = NULL; - config.inst_get_attrib_fn = NULL; - config.inst_set_attrib_fn = NULL; - config.load_script_fn = NULL; config.resolve_path_fn = NULL; config.user_data = NULL; @@ -857,17 +852,6 @@ L_vm_main_loop: DISPATCH(); } - OPCODE(PUSH_INSTANCE): - { - uint8_t index = READ_SHORT(); - ASSERT_INDEX(index, module->constants.count); - ASSERT(IS_OBJ_TYPE(module->constants.data[index], OBJ_CLASS), OOPS); - Instance* inst = newInstance(vm, - (Class*)AS_OBJ(module->constants.data[index]), false); - PUSH(VAR_OBJ(inst)); - DISPATCH(); - } - OPCODE(LIST_APPEND): { Var elem = PEEK(-1); // Don't pop yet, we need the reference for gc. @@ -898,21 +882,6 @@ L_vm_main_loop: DISPATCH(); } - OPCODE(INST_APPEND): - { - Var value = PEEK(-1); // Don't pop yet, we need the reference for gc. - Var inst = PEEK(-2); - ASSERT(IS_OBJ_TYPE(inst, OBJ_INST), OOPS); - - Instance* inst_p = (Instance*)AS_OBJ(inst); - ASSERT(!inst_p->is_native, OOPS); - Inst* ins = inst_p->ins; - pkVarBufferWrite(&ins->fields, vm, value); - DROP(); // value - - DISPATCH(); - } - OPCODE(PUSH_LOCAL_0): OPCODE(PUSH_LOCAL_1): OPCODE(PUSH_LOCAL_2): From 776ea0ab87ca6caf5239e3ba4c5c78bfda536307 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Wed, 20 Apr 2022 15:35:33 +0530 Subject: [PATCH 3/9] builtin types added. This commit adds the builtin types to the VM's builtin classes buffer (however no methods were added to those classes) and a super class parameter introduced while creating a new class. --- cli/modules/std_io.c | 2 +- src/include/pocketlang.h | 4 +++- src/pk_compiler.c | 39 +++++++++++++++++++++++++++++++++++---- src/pk_core.c | 22 +++++++++++++++++++--- src/pk_debug.c | 13 +++++++++++++ src/pk_opcodes.h | 4 ++++ src/pk_value.c | 5 +++-- src/pk_value.h | 7 +++++-- src/pk_vm.c | 9 +++++++++ 9 files changed, 92 insertions(+), 13 deletions(-) diff --git a/cli/modules/std_io.c b/cli/modules/std_io.c index aa944a9..fadaef0 100644 --- a/cli/modules/std_io.c +++ b/cli/modules/std_io.c @@ -171,7 +171,7 @@ static void _fileClose(PKVM* vm) { void registerModuleIO(PKVM* vm) { PkHandle* io = pkNewModule(vm, "io"); - PkHandle* cls_file = pkNewClass(vm, io, "File"); + PkHandle* cls_file = pkNewClass(vm, NULL, io, "File"); pkClassAddMethod(vm, cls_file, "open", _fileOpen, -1); pkClassAddMethod(vm, cls_file, "read", _fileRead, 0); pkClassAddMethod(vm, cls_file, "write", _fileWrite, 1); diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 58b9f46..fbcd18f 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -288,7 +288,9 @@ PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name); PK_PUBLIC void pkRegisterModule(PKVM* vm, PkHandle* module); // Create a new class on the [module] with the [name] and return it. -PK_PUBLIC PkHandle* pkNewClass(PKVM* vm, PkHandle* module, const char* name); +// If the [base_class] is NULL by default it'll set to "Object" class. +PK_PUBLIC PkHandle* pkNewClass(PKVM* vm, PkHandle* base_class, + PkHandle* module, const char* name); // Add a native method to the given class. If the [arity] is -1 that means // the method has variadic parameters and use pkGetArgc() to get the argc. diff --git a/src/pk_compiler.c b/src/pk_compiler.c index d4c9835..139af8a 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -1257,6 +1257,19 @@ static int findBuiltinFunction(const PKVM* vm, return -1; } +// Find the builtin classes name and returns it's index in the VM's builtin +// classes array, if not found returns -1. +static int findBuiltinClass(const PKVM* vm, + const char* name, uint32_t length) { + for (int i = 0; i < PK_INSTANCE; i++) { + uint32_t bfn_length = vm->builtin_classes[i]->name->length; + if (IS_CSTR_EQ(vm->builtin_classes[i]->name, name, length)) { + return i; + } + } + return -1; +} + // Find the local with the [name] in the given function [func] and return // it's index, if not found returns -1. static int findLocal(Func* func, const char* name, uint32_t length) { @@ -1337,7 +1350,8 @@ typedef enum { NAME_LOCAL_VAR, //< Including parameter. NAME_UPVALUE, //< Local to an enclosing function. NAME_GLOBAL_VAR, - NAME_BUILTIN_FN, //< Native builtin function. + NAME_BUILTIN_FN, //< Native builtin function. + NAME_BUILTIN_TY, //< Builtin primitive type classes. } NameDefnType; // Identifier search result. @@ -1394,6 +1408,13 @@ static NameSearchResult compilerSearchName(Compiler* compiler, return result; } + index = findBuiltinClass(compiler->parser.vm, name, length); + if (index != -1) { + result.type = NAME_BUILTIN_TY; + result.index = index; + return result; + } + return result; } @@ -1573,6 +1594,11 @@ static void emitPushName(Compiler* compiler, NameDefnType type, int index) { emitOpcode(compiler, OP_PUSH_BUILTIN_FN); emitByte(compiler, index); return; + + case NAME_BUILTIN_TY: + emitOpcode(compiler, OP_PUSH_BUILTIN_TY); + emitByte(compiler, index); + return; } } @@ -1584,6 +1610,7 @@ static void emitStoreName(Compiler* compiler, NameDefnType type, int index) { switch (type) { case NAME_NOT_DEFINED: case NAME_BUILTIN_FN: + case NAME_BUILTIN_TY: UNREACHABLE(); case NAME_LOCAL_VAR: @@ -1701,7 +1728,9 @@ static void exprName(Compiler* compiler) { // like python does) and it's recommented to define all the globals // before entering a local scope. - if (result.type == NAME_NOT_DEFINED || result.type == NAME_BUILTIN_FN) { + if (result.type == NAME_NOT_DEFINED || + result.type == NAME_BUILTIN_FN || + result.type == NAME_BUILTIN_TY ) { name_type = (compiler->scope_depth == DEPTH_GLOBAL) ? NAME_GLOBAL_VAR : NAME_LOCAL_VAR; @@ -2283,8 +2312,10 @@ static void compileClass(Compiler* compiler) { // Create a new class. int cls_index; - Class* cls = newClass(compiler->parser.vm, name, name_len, - compiler->module, NULL, &cls_index); + PKVM* _vm = compiler->parser.vm; + Class* cls = newClass(_vm, name, name_len, + _vm->builtin_classes[PK_OBJECT], compiler->module, + NULL, &cls_index); // Check count exceeded. checkMaxConstantsReached(compiler, cls_index); diff --git a/src/pk_core.c b/src/pk_core.c index 5d9ee61..f4b3307 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -69,13 +69,21 @@ void pkRegisterModule(PKVM* vm, PkHandle* module) { vmRegisterModule(vm, module_, module_->name); } -PkHandle* pkNewClass(PKVM* vm, PkHandle* module, const char* name) { +PkHandle* pkNewClass(PKVM* vm, PkHandle* base_class, PkHandle* module, + const char* name) { CHECK_NULL(module); CHECK_NULL(name); CHECK_TYPE(module, OBJ_MODULE); + Class* super = vm->builtin_classes[PK_OBJECT]; + if (base_class != NULL) { + CHECK_TYPE(base_class, OBJ_CLASS); + super = (Class*)AS_OBJ(base_class->value); + } + Class* class_ = newClass(vm, name, (int)strlen(name), - (Module*)AS_OBJ(module->value), NULL, NULL); + super, (Module*)AS_OBJ(module->value), + NULL, NULL); return vmNewHandle(vm, VAR_OBJ(class_)); } @@ -1222,7 +1230,15 @@ static void initializeCoreModules(PKVM* vm) { /*****************************************************************************/ static void initializePrimitiveClasses(PKVM* vm) { - // TODO + for (int i = 0; i < PK_INSTANCE; i++) { + Class* super = NULL; + if (i != 0) super = vm->builtin_classes[PK_OBJECT]; + const char* name = getPkVarTypeName((PkVarType)i); + vm->builtin_classes[i] = newClass(vm, name, (int)strlen(name), + super, NULL, NULL, NULL); + } + + // TODO: Add methods to those classes. } /*****************************************************************************/ diff --git a/src/pk_debug.c b/src/pk_debug.c index a86aff6..1a0f593 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -227,6 +227,19 @@ void dumpFunctionCode(PKVM* vm, Function* func) { break; } + case OP_PUSH_BUILTIN_TY: + { + int index = READ_BYTE(); + ASSERT_INDEX(index, PK_INSTANCE); + const char* name = vm->builtin_classes[index]->name->data; + // Prints: %5d [Fn:%s]\n + PRINT_INT(index); + PRINT(" [Class:"); + PRINT(name); + PRINT("]\n"); + break; + } + case OP_PUSH_UPVALUE: case OP_STORE_UPVALUE: { diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index 1a7a561..f329f96 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -92,6 +92,10 @@ OPCODE(STORE_GLOBAL, 1, 0) // params: 1 bytes index. OPCODE(PUSH_BUILTIN_FN, 1, 1) +// Push a built in class. +// params: 1 bytes index. +OPCODE(PUSH_BUILTIN_TY, 1, 1) + // Push an upvalue of the current closure at the index which is the first one // byte argument. // params: 1 byte index. diff --git a/src/pk_value.c b/src/pk_value.c index 07bcfad..21829a8 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -492,8 +492,8 @@ Fiber* newFiber(PKVM* vm, Closure* closure) { } Class* newClass(PKVM* vm, const char* name, int length, - Module* module, const char* docstring, - int* cls_index) { + Class* super, Module* module, + const char* docstring, int* cls_index) { Class* cls = ALLOCATE(vm, Class); varInitObject(&cls->_super, vm, OBJ_CLASS); @@ -501,6 +501,7 @@ Class* newClass(PKVM* vm, const char* name, int length, vmPushTempRef(vm, &cls->_super); // class. cls->owner = NULL; + cls->super_class = super; cls->docstring = NULL; cls->ctor = NULL; diff --git a/src/pk_value.h b/src/pk_value.h index b8e80c2..520026e 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -480,6 +480,9 @@ struct Fiber { struct Class { Object _super; + // The base class of this class. + Class* super_class; + // The module that owns this class. Module* owner; @@ -566,8 +569,8 @@ Function* newFunction(PKVM* vm, const char* name, int length, // the module's constant pool. The class will be added to the modules global // as well. Class* newClass(PKVM* vm, const char* name, int length, - Module* module, const char* docstring, - int* cls_index); + Class* super, Module* module, + const char* docstring, int* cls_index); // Allocate new instance with of the base [type]. Instance* newInstance(PKVM* vm, Class* cls); diff --git a/src/pk_vm.c b/src/pk_vm.c index 4de2829..5c975f4 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -949,6 +949,15 @@ L_vm_main_loop: DISPATCH(); } + OPCODE(PUSH_BUILTIN_TY): + { + uint8_t index = READ_BYTE(); + ASSERT_INDEX(index, PK_INSTANCE); + Class* cls = vm->builtin_classes[index]; + PUSH(VAR_OBJ(cls)); + DISPATCH(); + } + OPCODE(PUSH_UPVALUE): { uint8_t index = READ_BYTE(); From 67ab2057c0f991d41f7f4cd14cd49d445c26a54c Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Wed, 20 Apr 2022 16:41:57 +0530 Subject: [PATCH 4/9] method buffer added to classes and also allocation/de-allocation callbacks were added to newClass function and yet registering attribute getters and setters is to do. --- cli/main.c | 3 +-- cli/modules/std_io.c | 29 ++++++++++++++--------------- src/include/pocketlang.h | 15 +++++++++++++-- src/pk_core.c | 25 +++++++++++++++++++++---- src/pk_value.c | 29 ++++++++++++++++++++++++++--- src/pk_value.h | 10 ++++++++++ 6 files changed, 85 insertions(+), 26 deletions(-) diff --git a/cli/main.c b/cli/main.c index 3c317eb..09ff9e4 100644 --- a/cli/main.c +++ b/cli/main.c @@ -188,8 +188,7 @@ int main(int argc, const char** argv) { user_data.repl_mode = false; pkSetUserData(vm, &user_data); - registerModulePath(vm); - //REGISTER_ALL_MODULES(vm); + REGISTER_ALL_MODULES(vm); PkCompileOptions options = pkNewCompilerOptions(); options.debug = debug; diff --git a/cli/modules/std_io.c b/cli/modules/std_io.c index fadaef0..e2c6402 100644 --- a/cli/modules/std_io.c +++ b/cli/modules/std_io.c @@ -6,6 +6,10 @@ #include "modules.h" +/*****************************************************************************/ +/* FILE CLASS */ +/*****************************************************************************/ + // Str | If already exists | If does not exist | // -----+-------------------+-------------------| // 'r' | read from start | failure to open | @@ -31,26 +35,21 @@ typedef struct { bool closed; // True if the file isn't closed yet. } File; -/*****************************************************************************/ -/* FILE OBJECT OPERATORS */ -/*****************************************************************************/ - -void fileGetAttrib(PKVM* vm, File* file, const char* attrib) { - if (strcmp(attrib, "closed") == 0) { - pkReturnBool(vm, file->closed); - return; - } +void* _newFile() { + File* file = NEW_OBJ(File); + file->closed = true; + file->mode = FMODE_NONE; + file->fp = NULL; + return file; } -bool fileSetAttrib(PKVM* vm, File* file, const char* attrib) { - return false; -} - -void fileClean(PKVM* vm, File* file) { +void _deleteFile(void* ptr) { + File* file = (File*)ptr; if (!file->closed) { if (fclose(file->fp) != 0) { /* TODO: error! */ } file->closed = true; } + FREE_OBJ(file); } /*****************************************************************************/ @@ -171,7 +170,7 @@ static void _fileClose(PKVM* vm) { void registerModuleIO(PKVM* vm) { PkHandle* io = pkNewModule(vm, "io"); - PkHandle* cls_file = pkNewClass(vm, NULL, io, "File"); + PkHandle* cls_file = pkNewClass(vm, "File", NULL, io, _newFile, _deleteFile); pkClassAddMethod(vm, cls_file, "open", _fileOpen, -1); pkClassAddMethod(vm, cls_file, "read", _fileRead, 0); pkClassAddMethod(vm, cls_file, "write", _fileWrite, 1); diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index fbcd18f..10690df 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -127,6 +127,15 @@ typedef PkStringPtr (*pkResolvePathFn) (PKVM* vm, const char* from, // to indicate if it's failed to load the script. typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path); +// A function callback to allocate and return a new instance of the registered +// class. Which will be called when the instance is constructed. The returned/ +// data is expected to be alive till the delete callback occurs. +typedef void* (*pkNewInstanceFn) (); + +// A function callback to de-allocate the aloocated native instance of the +// registered class. +typedef void (*pkDeleteInstanceFn) (void*); + /*****************************************************************************/ /* POCKETLANG TYPES */ /*****************************************************************************/ @@ -289,8 +298,10 @@ PK_PUBLIC void pkRegisterModule(PKVM* vm, PkHandle* module); // Create a new class on the [module] with the [name] and return it. // If the [base_class] is NULL by default it'll set to "Object" class. -PK_PUBLIC PkHandle* pkNewClass(PKVM* vm, PkHandle* base_class, - PkHandle* module, const char* name); +PK_PUBLIC PkHandle* pkNewClass(PKVM* vm, const char* name, + PkHandle* base_class, PkHandle* module, + pkNewInstanceFn new_fn, + pkDeleteInstanceFn delete_fn); // Add a native method to the given class. If the [arity] is -1 that means // the method has variadic parameters and use pkGetArgc() to get the argc. diff --git a/src/pk_core.c b/src/pk_core.c index f4b3307..c13fc47 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -69,8 +69,10 @@ void pkRegisterModule(PKVM* vm, PkHandle* module) { vmRegisterModule(vm, module_, module_->name); } -PkHandle* pkNewClass(PKVM* vm, PkHandle* base_class, PkHandle* module, - const char* name) { +PkHandle* pkNewClass(PKVM* vm, const char* name, + PkHandle* base_class, PkHandle* module, + pkNewInstanceFn new_fn, + pkDeleteInstanceFn delete_fn) { CHECK_NULL(module); CHECK_NULL(name); CHECK_TYPE(module, OBJ_MODULE); @@ -84,6 +86,9 @@ PkHandle* pkNewClass(PKVM* vm, PkHandle* base_class, PkHandle* module, Class* class_ = newClass(vm, name, (int)strlen(name), super, (Module*)AS_OBJ(module->value), NULL, NULL); + class_->new_fn = new_fn; + class_->delete_fn = delete_fn; + return vmNewHandle(vm, VAR_OBJ(class_)); } @@ -92,9 +97,21 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls, pkNativeFn fptr, int arity) { CHECK_NULL(cls); CHECK_NULL(fptr); - CHECK_TYPE(cls, OBJ_MODULE); + CHECK_TYPE(cls, OBJ_CLASS); - TODO; + Class* class_ = (Class*)AS_OBJ(cls->value); + + Function* fn = newFunction(vm, name, (int)strlen(name), + class_->owner, true, NULL, NULL); + + // No need to push the function to temp references of the VM + // since it's written to the constant pool of the module and the module + // won't be garbage collected (class handle has reference to the module). + + Closure* method = newClosure(vm, fn); + vmPushTempRef(vm, &method->_super); // method. + pkClosureBufferWrite(&class_->methods, vm, method); + vmPopTempRef(vm); // method. } void* pkGetSelf(const PKVM* vm) { diff --git a/src/pk_value.c b/src/pk_value.c index 21829a8..4e31f85 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -94,6 +94,7 @@ DEFINE_BUFFER(Uint, uint32_t) DEFINE_BUFFER(Byte, uint8_t) DEFINE_BUFFER(Var, Var) DEFINE_BUFFER(String, String*) +DEFINE_BUFFER(Closure, Closure*) void pkByteBufferAddString(pkByteBuffer* self, PKVM* vm, const char* str, uint32_t length) { @@ -146,6 +147,13 @@ void markStringBuffer(PKVM* vm, pkStringBuffer* self) { } } +void markClosureBuffer(PKVM* vm, pkClosureBuffer* self) { + if (self == NULL) return; + for (uint32_t i = 0; i < self->count; i++) { + markObject(vm, &self->data[i]->_super); + } +} + static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { // TODO: trace here. @@ -271,6 +279,10 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { markObject(vm, &cls->owner->_super); markObject(vm, &cls->ctor->_super); markObject(vm, &cls->name->_super); + + markClosureBuffer(vm, &cls->methods); + vm->bytes_allocated += sizeof(Closure) * cls->methods.capacity; + } break; case OBJ_INST: @@ -500,10 +512,14 @@ Class* newClass(PKVM* vm, const char* name, int length, vmPushTempRef(vm, &cls->_super); // class. + pkClosureBufferInit(&cls->methods); + cls->owner = NULL; cls->super_class = super; cls->docstring = NULL; cls->ctor = NULL; + cls->new_fn = NULL; + cls->delete_fn = NULL; // Builtin types doesn't belongs to a module. if (module != NULL) { @@ -523,7 +539,13 @@ Instance* newInstance(PKVM* vm, Class* cls) { Instance* inst = ALLOCATE(vm, Instance); varInitObject(&inst->_super, vm, OBJ_INST); inst->cls = cls; - inst->native = NULL; + if (cls->new_fn != NULL) { + vmPushTempRef(vm, &inst->_super); // inst. + inst->native = cls->new_fn(); + vmPopTempRef(vm); // inst. + } else { + inst->native = NULL; + } return inst; } @@ -1016,12 +1038,13 @@ void freeObject(PKVM* vm, Object* self) { case OBJ_CLASS: { Class* cls = (Class*)self; + pkClosureBufferClear(&cls->methods, vm); } break; case OBJ_INST: { Instance* inst = (Instance*)self; - if (inst->native) { - TODO; // Call native clean function. + if (inst->cls->delete_fn != NULL) { + inst->cls->delete_fn(inst->native); } } break; } diff --git a/src/pk_value.h b/src/pk_value.h index 520026e..8858b48 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -200,6 +200,7 @@ DECLARE_BUFFER(Uint, uint32_t) DECLARE_BUFFER(Byte, uint8_t) DECLARE_BUFFER(Var, Var) DECLARE_BUFFER(String, String*) +DECLARE_BUFFER(Closure, Closure*) // Add all the characters to the buffer, byte buffer can also be used as a // buffer to write string (like a string stream). Note that this will not @@ -494,6 +495,15 @@ struct Class { const char* docstring; Closure* ctor; //< The constructor function. + + // A buffer of methods of the class. + pkClosureBuffer methods; + + // Allocater and de-allocator functions for native types. + // For script/ builtin types it'll be NULL. + pkNewInstanceFn new_fn; + pkDeleteInstanceFn delete_fn; + }; typedef struct { From 167355be5952b8c4e3fcc03c80d2f5ef402b70ed Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Wed, 20 Apr 2022 20:38:23 +0530 Subject: [PATCH 5/9] getMethod and preConstructSelf functions impl --- src/pk_core.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++++- src/pk_core.h | 18 ++++++ src/pk_value.c | 23 +++++++ src/pk_value.h | 3 + 4 files changed, 205 insertions(+), 2 deletions(-) diff --git a/src/pk_core.c b/src/pk_core.c index c13fc47..e8bd365 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -1243,7 +1243,66 @@ static void initializeCoreModules(PKVM* vm) { #undef DEF /*****************************************************************************/ -/* PRIMITIVE TYPES CLASS */ +/* BUILTIN CLASS METHODS */ +/*****************************************************************************/ + +static forceinline void _setSelf(PKVM* vm, Var value) { + vm->fiber->frames[vm->fiber->frame_count - 1].self = value; +} + +static void _ctorNull(PKVM* vm) { + _setSelf(vm, VAR_NULL); +} + +static void _ctorBool(PKVM* vm) { + _setSelf(vm, toBool(ARG(1))); +} + +static void _ctorNumber(PKVM* vm) { + double value; + if (!validateNumeric(vm, ARG(1), &value, "Argument 1")) return; + _setSelf(vm, VAR_NUM(value)); +} + +static void _ctorString(PKVM* vm) { + if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return; + if (ARGC == 0) { + _setSelf(vm, VAR_OBJ(newStringLength(vm, NULL, 0))); + return; + } + _setSelf(vm, VAR_OBJ(toString(vm, ARG(1)))); +} + +static void _ctorList(PKVM* vm) { + List* list = newList(vm, ARGC); + vmPushTempRef(vm, &list->_super); // list. + for (int i = 0; i < ARGC; i++) { + listAppend(vm, list, ARG(i + 1)); + } + vmPopTempRef(vm); // list. + _setSelf(vm, VAR_OBJ(list)); +} + +static void _ctorMap(PKVM* vm) { + _setSelf(vm, VAR_OBJ(newMap(vm))); +} + +static void _ctorRange(PKVM* vm) { + double from, to; + if (!validateNumeric(vm, ARG(1), &from, "Argument 1")) return; + if (!validateNumeric(vm, ARG(2), &to, "Argument 2")) return; + + _setSelf(vm, VAR_OBJ(newRange(vm, from, to))); +} + +static void _ctorFiber(PKVM* vm) { + Closure* closure; + if (!validateArgClosure(vm, 1, &closure)) return; + _setSelf(vm, VAR_OBJ(newFiber(vm, closure))); +} + +/*****************************************************************************/ +/* BUILTIN CLASS INITIALIZATION */ /*****************************************************************************/ static void initializePrimitiveClasses(PKVM* vm) { @@ -1255,13 +1314,113 @@ static void initializePrimitiveClasses(PKVM* vm) { super, NULL, NULL, NULL); } - // TODO: Add methods to those classes. +#define ADD_CTOR(type, name, ptr, arity_) \ + do { \ + Function* fn = newFunction(vm, name, (int)strlen(name), \ + NULL, true, NULL, NULL); \ + fn->native = ptr; \ + fn->arity = arity_; \ + vmPushTempRef(vm, &fn->_super); /* fn. */ \ + vm->builtin_classes[type]->ctor = newClosure(vm, fn); \ + vmPopTempRef(vm); /* fn. */ \ + } while (false) + + ADD_CTOR(PK_NULL, "@ctorNull", _ctorNull, 0); + ADD_CTOR(PK_BOOL, "@ctorBool", _ctorBool, 1); + ADD_CTOR(PK_NUMBER, "@ctorNumber", _ctorNumber, 1); + ADD_CTOR(PK_STRING, "@ctorString", _ctorString, -1); + ADD_CTOR(PK_LIST, "@ctorList", _ctorList, -1); + ADD_CTOR(PK_MAP, "@ctorMap", _ctorMap, 0); + ADD_CTOR(PK_FIBER, "@ctorFiber", _ctorFiber, 1); + + // TODO: add methods. + } /*****************************************************************************/ /* OPERATORS */ /*****************************************************************************/ +Var preConstructSelf(PKVM* vm, Class* cls) { + +#define NO_INSTANCE(type_name) \ + VM_SET_ERROR(vm, newString(vm, \ + "Class '" type_name "' cannot be instanciated.")) + + for (int i = 0; i < PK_INSTANCE; i++) { + if (vm->builtin_classes[i] == cls) { + + switch ((PkVarType)i) { + case PK_OBJECT: + NO_INSTANCE("Object"); + return VAR_NULL; + + case PK_NULL: + case PK_BOOL: + case PK_NUMBER: + case PK_STRING: + case PK_LIST: + case PK_MAP: + case PK_RANGE: + return VAR_NULL; // Constructor will override the null. + + case PK_MODULE: + NO_INSTANCE("Module"); + return VAR_NULL; + + case PK_CLOSURE: + NO_INSTANCE("Closure"); + return VAR_NULL; + + case PK_FIBER: + return VAR_NULL; + + case PK_CLASS: + NO_INSTANCE("Class"); + return VAR_NULL; + + case PK_INSTANCE: + UNREACHABLE(); + return VAR_NULL; + } + } + } + + return VAR_OBJ(newInstance(vm, cls)); +} + +Class* getClass(PKVM* vm, Var instance) { + PkVarType type = getVarType(instance); + if (0 <= type && type < PK_INSTANCE) { + return vm->builtin_classes[type]; + } + ASSERT(IS_OBJ_TYPE(instance, OBJ_INST), OOPS); + Instance* inst = (Instance*)AS_OBJ(instance); + return inst->cls; +} + +Var getMethod(PKVM* vm, Var self, String* name, bool* is_method) { + + Class* cls = getClass(vm, self); + ASSERT(cls != NULL, OOPS); + + Class* cls_ = cls; + do { + for (int i = 0; i < (int)cls_->methods.count; i++) { + Closure* method = cls_->methods.data[i]; + if (IS_CSTR_EQ(name, method->fn->name, name->length)) { + if (is_method) *is_method = true; + return VAR_OBJ(method); + } + } + cls_ = cls_->super_class; + } while (cls_ != NULL); + + // If the attribute not found it'll set an error. + if (is_method) *is_method = false; + return varGetAttrib(vm, self, name); +} + #define UNSUPPORTED_OPERAND_TYPES(op) \ VM_SET_ERROR(vm, stringFormat(vm, "Unsupported operand types for " \ "operator '" op "' $ and $", varTypeName(v1), varTypeName(v2))) diff --git a/src/pk_core.h b/src/pk_core.h index cfe7fe3..6bf294e 100644 --- a/src/pk_core.h +++ b/src/pk_core.h @@ -17,6 +17,24 @@ void initializeCore(PKVM* vm); /* OPERATORS */ /*****************************************************************************/ +// This method is called just before constructing a type to initialize self +// and after that the constructor will be called. For builtin types this +// function will return VAR_NULL and the constructor will override self to +// it's instance (because for some classes we cannot create without argument +// example Fiber(fn), Range(from, to) etc). If the class cannot be +// instanciated (ex: Class 'Module') it'll set an error and return VAR_NULL. +// For other classes the return value will be an Instance. +Var preConstructSelf(PKVM* vm, Class* cls); + +// Returns the class of the [instance]. +Class* getClass(PKVM* vm, Var instance); + +// Returns the method (closure) in the instance [self]. If it's not an method +// but just an attribute the [is_method] pointer will be set to false and +// returns the value. +// If the method / attribute not found, it'll set a runtime error on the VM. +Var getMethod(PKVM* vm, Var self, String* name, bool* is_method); + Var varAdd(PKVM* vm, Var v1, Var v2); // Returns v1 + v2. Var varSubtract(PKVM* vm, Var v1, Var v2); // Returns v1 - v2. Var varMultiply(PKVM* vm, Var v1, Var v2); // Returns v1 * v2. diff --git a/src/pk_value.c b/src/pk_value.c index 4e31f85..ebfb174 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -536,6 +536,19 @@ Class* newClass(PKVM* vm, const char* name, int length, } Instance* newInstance(PKVM* vm, Class* cls) { + +#ifdef DEBUG + bool _builtin_class = false; + for (int i = 0; i < PK_INSTANCE; i++) { + if (vm->builtin_classes[i] == cls) { + _builtin_class = true; + break; + } + } + ASSERT(!_builtin_class, "Cannot create an instace of builtin class " + "with newInstance() function."); +#endif // DEBUG + Instance* inst = ALLOCATE(vm, Instance); varInitObject(&inst->_super, vm, OBJ_INST); inst->cls = cls; @@ -1272,6 +1285,16 @@ const char* varTypeName(Var v) { return getObjectTypeName(obj->type); } +PkVarType getVarType(Var v) { + if (IS_NULL(v)) return PK_NULL; + if (IS_BOOL(v)) return PK_BOOL; + if (IS_NUM(v)) return PK_NUMBER; + + ASSERT(IS_OBJ(v), OOPS); + Object* obj = AS_OBJ(v); + return getObjPkVarType(obj->type); +} + bool isValuesSame(Var v1, Var v2) { #if VAR_NAN_TAGGING // Bit representation of each values are unique so just compare the bits. diff --git a/src/pk_value.h b/src/pk_value.h index 8858b48..13a6609 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -744,6 +744,9 @@ const char* getObjectTypeName(ObjectType type); // Returns the type name of the var [v]. const char* varTypeName(Var v); +// Returns the PkVarType of the first class varaible [v]. +PkVarType getVarType(Var v); + // Returns true if both variables are the same (ie v1 is v2). bool isValuesSame(Var v1, Var v2); From 03bac026eee1928d14b83a1bcbf6644607541079 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Thu, 21 Apr 2022 06:59:47 +0530 Subject: [PATCH 6/9] `METHOD_CALL` opcode implemented As of this commit there is only one method in the entier pocketlang thats List.append() have added (the reset is todo). method searching algorithm should be optimized in the future by sorting the methods according to their names and do a binary search --- src/pk_compiler.c | 26 ++++++++++++++++--- src/pk_core.c | 44 +++++++++++++++++++++++++++----- src/pk_debug.c | 18 +++++++++++++ src/pk_opcodes.h | 5 ++++ src/pk_value.c | 1 + src/pk_value.h | 7 +++++ src/pk_vm.c | 65 ++++++++++++++++++++++++++++++++--------------- 7 files changed, 136 insertions(+), 30 deletions(-) diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 139af8a..8fd9994 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -1926,7 +1926,11 @@ static void exprMap(Compiler* compiler) { consume(compiler, TK_RBRACE, "Expected '}' after map elements."); } -static void exprCall(Compiler* compiler) { +// This function is reused between calls and method calls. if the [call_type] +// is OP_METHOD_CALL the [method] should refer a string in the module's +// constant pool, otherwise it's ignored. +static void _compileCall(Compiler* compiler, Opcode call_type, int method) { + ASSERT((call_type == OP_CALL) || (call_type == OP_METHOD_CALL), OOPS); // Compile parameters. int argc = 0; @@ -1940,14 +1944,24 @@ static void exprCall(Compiler* compiler) { consume(compiler, TK_RPARAN, "Expected ')' after parameter list."); } - emitOpcode(compiler, OP_CALL); + emitOpcode(compiler, call_type); + emitByte(compiler, argc); + if (call_type == OP_METHOD_CALL) { + ASSERT_INDEX(method, (int)compiler->module->constants.count); + emitShort(compiler, method); + } + // After the call the arguments will be popped and the callable // will be replaced with the return value. compilerChangeStack(compiler, -argc); } +static void exprCall(Compiler* compiler) { + _compileCall(compiler, OP_CALL, -1); +} + static void exprAttrib(Compiler* compiler) { consume(compiler, TK_NAME, "Expected an attribute name after '.'."); const char* name = compiler->parser.previous.start; @@ -1958,6 +1972,12 @@ static void exprAttrib(Compiler* compiler) { moduleAddString(compiler->module, compiler->parser.vm, name, length, &index); + // Check if it's a method call. + if (match(compiler, TK_LPARAN)) { + _compileCall(compiler, OP_METHOD_CALL, index); + return; + } + if (compiler->l_value && matchAssignment(compiler)) { TokenType assignment = compiler->parser.previous.type; skipNewLines(compiler); @@ -3168,7 +3188,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source, } #if DUMP_BYTECODE - dumpFunctionCode(compiler->parser.vm, module->body); + dumpFunctionCode(compiler->parser.vm, module->body->fn); #endif // Return the compilation result. diff --git a/src/pk_core.c b/src/pk_core.c index e8bd365..456d750 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -115,9 +115,8 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls, } void* pkGetSelf(const PKVM* vm) { - Var self = vm->fiber->frames[vm->fiber->frame_count - 1].self; - ASSERT(IS_OBJ_TYPE(self, OBJ_INST), OOPS); - Instance* inst = (Instance*)AS_OBJ(self); + ASSERT(IS_OBJ_TYPE(vm->fiber->self, OBJ_INST), OOPS); + Instance* inst = (Instance*)AS_OBJ(vm->fiber->self); ASSERT(inst->native != NULL, OOPS); return inst->native; } @@ -1243,11 +1242,15 @@ static void initializeCoreModules(PKVM* vm) { #undef DEF /*****************************************************************************/ -/* BUILTIN CLASS METHODS */ +/* BUILTIN CLASS CONSTRUCTORS */ /*****************************************************************************/ -static forceinline void _setSelf(PKVM* vm, Var value) { - vm->fiber->frames[vm->fiber->frame_count - 1].self = value; +static inline void _setSelf(PKVM* vm, Var value) { + vm->fiber->self = value; +} + +static inline Var _getSelf(PKVM* vm) { + return vm->fiber->self; } static void _ctorNull(PKVM* vm) { @@ -1301,6 +1304,17 @@ static void _ctorFiber(PKVM* vm) { _setSelf(vm, VAR_OBJ(newFiber(vm, closure))); } +/*****************************************************************************/ +/* BUILTIN CLASS METHODS */ +/*****************************************************************************/ + +static void _listAppend(PKVM* vm) { + Var self = _getSelf(vm); + ASSERT(IS_OBJ_TYPE(self, OBJ_LIST), OOPS); + listAppend(vm, ((List*)AS_OBJ(self)), ARG(1)); + RET(self); +} + /*****************************************************************************/ /* BUILTIN CLASS INITIALIZATION */ /*****************************************************************************/ @@ -1333,7 +1347,23 @@ static void initializePrimitiveClasses(PKVM* vm) { ADD_CTOR(PK_MAP, "@ctorMap", _ctorMap, 0); ADD_CTOR(PK_FIBER, "@ctorFiber", _ctorFiber, 1); - // TODO: add methods. +#undef ADD_CTOR + +#define ADD_METHOD(type, name, ptr, arity_) \ + do { \ + Function* fn = newFunction(vm, name, (int)strlen(name), \ + NULL, true, NULL, NULL); \ + fn->native = ptr; \ + fn->arity = arity_; \ + vmPushTempRef(vm, &fn->_super); /* fn. */ \ + pkClosureBufferWrite(&vm->builtin_classes[type]->methods, \ + vm, newClosure(vm, fn)); \ + vmPopTempRef(vm); /* fn. */ \ + } while (false) + + ADD_METHOD(PK_LIST, "append", _listAppend, 1); + +#undef ADD_METHOD } diff --git a/src/pk_debug.c b/src/pk_debug.c index 1a0f593..2525731 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -282,6 +282,24 @@ void dumpFunctionCode(PKVM* vm, Function* func) { break; } + case OP_METHOD_CALL: + { + int argc = READ_BYTE(); + int index = READ_SHORT(); + String* name = moduleGetStringAt(func->owner, index); + ASSERT(name != NULL, OOPS); + + // Prints: %5d (argc) %d '%s'\n + PRINT_INT(argc); + PRINT(" (argc) "); + + _PRINT_INT(index, 0); + PRINT(" '"); + PRINT(name->data); + PRINT("'\n"); + break; + } + case OP_CALL: // Prints: %5d (argc)\n PRINT_INT(READ_BYTE()); diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index f329f96..bf6fd73 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -123,6 +123,11 @@ OPCODE(POP, 0, -1) // params: 2 byte name index. OPCODE(IMPORT, 2, 1) +// Call a method on the variable at the stack top. See opcode CALL for detail. +// params: 2 bytes method name index in the constant pool. +// 1 byte argc. +OPCODE(METHOD_CALL, 3, -0) //< Stack size will be calculated at compile time. + // Calls a function using stack's top N values as the arguments and once it // done the stack top should be stored otherwise it'll be disregarded. The // function should set the 0 th argment to return value. diff --git a/src/pk_value.c b/src/pk_value.c index ebfb174..dba3176 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -495,6 +495,7 @@ Fiber* newFiber(PKVM* vm, Closure* closure) { } fiber->open_upvalues = NULL; + fiber->self = VAR_UNDEFINED; // Initialize the return value to null (doesn't really have to do that here // but if we're trying to debut it may crash when dumping the return value). diff --git a/src/pk_value.h b/src/pk_value.h index 13a6609..20d128f 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -466,6 +466,13 @@ struct Fiber { // overflowed. Var* ret; + // The self pointer to of the current method. It'll be updated before + // calling a native method. (Because native methods doesn't have a call + // frame we're doing it this way). Also updated just before calling a + // script method, and will be captured by the next allocated callframe + // and reset to VAR_UNDEFINED. + Var self; + // Heap allocated array of call frames will grow as needed. CallFrame* frames; int frame_capacity; //< Capacity of the frames array. diff --git a/src/pk_vm.c b/src/pk_vm.c index 5c975f4..2b9847a 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -550,7 +550,10 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) { frame->rbp = rbp; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; - frame->self = VAR_UNDEFINED; + + // Eat the self. + frame->self = vm->fiber->self; + vm->fiber->self = VAR_UNDEFINED; } static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { @@ -564,6 +567,7 @@ static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { CallFrame* frame = fb->frames + fb->frame_count - 1; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; + frame->self = VAR_UNDEFINED; ASSERT(*frame->rbp == VAR_NULL, OOPS); @@ -791,12 +795,18 @@ L_vm_main_loop: // defined, the next line become a declaration (Opcode instruction;). NO_OP; +#define _DUMP_STACK() \ + do { \ + system("cls"); /* FIXME: */ \ + dumpGlobalValues(vm); \ + dumpStackFrame(vm); \ + DEBUG_BREAK(); \ + } while (false) + #if DUMP_STACK - system("cls"); // FIXME: - dumpGlobalValues(vm); - dumpStackFrame(vm); - DEBUG_BREAK(); + _DUMP_STACK(); #endif +#undef _DUMP_STACK SWITCH() { @@ -1045,32 +1055,49 @@ L_vm_main_loop: DISPATCH(); } + { + uint8_t argc; + Var callable; + const Closure* closure; + + OPCODE(METHOD_CALL): + argc = READ_BYTE(); + fiber->ret = (fiber->sp - argc - 1); + fiber->self = *fiber->ret; //< Self for the next call. + + uint16_t index = READ_SHORT(); + bool is_method; + String* name = moduleGetStringAt(module, (int)index); + callable = getMethod(vm, fiber->self, name, &is_method); + CHECK_ERROR(); + goto L_do_call; + OPCODE(CALL): OPCODE(TAIL_CALL): - { - const uint8_t argc = READ_BYTE(); - Var* callable = fiber->sp - argc - 1; - - const Closure* closure = NULL; + argc = READ_BYTE(); + fiber->ret = fiber->sp - argc - 1; + callable = *fiber->ret; +L_do_call: // Raw functions cannot be on the stack, since they're not first class // citizens. - ASSERT(!IS_OBJ_TYPE(*callable, OBJ_FUNC), OOPS); + ASSERT(!IS_OBJ_TYPE(callable, OBJ_FUNC), OOPS); - if (IS_OBJ_TYPE(*callable, OBJ_CLOSURE)) { - closure = (const Closure*)AS_OBJ(*callable); + if (IS_OBJ_TYPE(callable, OBJ_CLOSURE)) { + closure = (const Closure*)AS_OBJ(callable); - } else if (IS_OBJ_TYPE(*callable, OBJ_CLASS)) { - closure = (const Closure*)((Class*)AS_OBJ(*callable))->ctor; + } else if (IS_OBJ_TYPE(callable, OBJ_CLASS)) { + closure = (const Closure*)((Class*)AS_OBJ(callable))->ctor; } else { RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a callable to " "call, instead got", - varTypeName(*callable), toString(vm, *callable))); + varTypeName(callable), toString(vm, callable))); DISPATCH(); } // If we reached here it's a valid callable. + ASSERT(closure != NULL, OOPS); // -1 argument means multiple number of args. if (closure->fn->arity != -1 && closure->fn->arity != argc) { @@ -1080,8 +1107,6 @@ L_vm_main_loop: RUNTIME_ERROR(msg); } - // Next call frame starts here. (including return value). - fiber->ret = callable; *(fiber->ret) = VAR_NULL; //< Set the return value to null. if (closure->fn->is_native) { @@ -1116,9 +1141,9 @@ L_vm_main_loop: } else { - if (instruction == OP_CALL) { + if (instruction == OP_CALL || instruction == OP_METHOD_CALL) { UPDATE_FRAME(); //< Update the current frame's ip. - pushCallFrame(vm, closure, callable); + pushCallFrame(vm, closure, fiber->ret); LOAD_FRAME(); //< Load the top frame to vm's execution variables. } else { From 1951b3d5f7aeeca5af804095921560d495a1ae5e Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Thu, 21 Apr 2022 08:36:37 +0530 Subject: [PATCH 7/9] self initialization and constructor calls implemented fibers are moved to builtin class and the fiber module has removed. native docstring support required to move math to cli modules (TODO) --- src/pk_core.c | 186 ++++++++++++++++++------------------------- src/pk_vm.c | 21 ++++- tests/lang/fibers.pk | 23 +++--- 3 files changed, 108 insertions(+), 122 deletions(-) diff --git a/src/pk_core.c b/src/pk_core.c index 456d750..7d200da 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -122,7 +122,7 @@ void* pkGetSelf(const PKVM* vm) { } void pkModuleAddGlobal(PKVM* vm, PkHandle* module, - const char* name, PkHandle* value) { + const char* name, PkHandle* value) { CHECK_TYPE(module, OBJ_MODULE); CHECK_NULL(value); @@ -1120,68 +1120,6 @@ DEF(stdMathRound, RET(VAR_NUM(round(num))); } -// 'Fiber' module methods. -// ----------------------- - -DEF(stdFiberNew, - "new(fn:Closure) -> fiber\n" - "Create and return a new fiber from the given function [fn].") { - - Closure* closure; - if (!validateArgClosure(vm, 1, &closure)) return; - RET(VAR_OBJ(newFiber(vm, closure))); -} - -DEF(stdFiberRun, - "run(fb:Fiber, ...) -> var\n" - "Runs the fiber's function with the provided arguments and returns it's " - "return value or the yielded value if it's yielded.") { - - int argc = ARGC; - if (argc == 0) // Missing the fiber argument. - RET_ERR(newString(vm, "Missing argument - fiber.")); - - Fiber* fb; - if (!validateArgFiber(vm, 1, &fb)) return; - - // Buffer of argument to call vmPrepareFiber(). - Var* args[MAX_ARGC]; - - // ARG(1) is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc). - for (int i = 1; i < argc; i++) { - args[i - 1] = &ARG(i + 1); - } - - // Switch fiber and start execution. - if (vmPrepareFiber(vm, fb, argc - 1, args)) { - ASSERT(fb == vm->fiber, OOPS); - fb->state = FIBER_RUNNING; - } -} - -DEF(stdFiberResume, - "resume(fb:Fiber) -> var\n" - "Resumes a yielded function from a previous call of fiber_run() function. " - "Return it's return value or the yielded value if it's yielded.") { - - int argc = ARGC; - if (argc == 0) // Missing the fiber argument. - RET_ERR(newString(vm, "Expected at least 1 argument(s).")); - if (argc > 2) // Can only accept 1 argument for resume. - RET_ERR(newString(vm, "Expected at most 2 argument(s).")); - - Fiber* fb; - if (!validateArgFiber(vm, 1, &fb)) return; - - Var value = (argc == 1) ? VAR_NULL : ARG(2); - - // Switch fiber and resume execution. - if (vmSwitchFiber(vm, fb, &value)) { - ASSERT(fb == vm->fiber, OOPS); - fb->state = FIBER_RUNNING; - } -} - static void initializeCoreModules(PKVM* vm) { #define MODULE_ADD_FN(module, name, fn, argc) \ moduleAddFunctionInternal(vm, module, name, fn, argc, DOCSTRING(fn)) @@ -1226,54 +1164,35 @@ static void initializeCoreModules(PKVM* vm) { // modify the PI, like in python. moduleAddGlobal(vm, math, "PI", 2, VAR_NUM(M_PI)); - // FIXME: - // Temproarly rename the fiber to move it to the builtin type classes. - NEW_MODULE(fiber, "_Fiber"); - MODULE_ADD_FN(fiber, "new", stdFiberNew, 1); - MODULE_ADD_FN(fiber, "run", stdFiberRun, -1); - MODULE_ADD_FN(fiber, "resume", stdFiberResume, -1); - #undef MODULE_ADD_FN #undef NEW_MODULE } -#undef IS_NUM_BYTE -#undef DOCSTRING -#undef DEF - /*****************************************************************************/ /* BUILTIN CLASS CONSTRUCTORS */ /*****************************************************************************/ -static inline void _setSelf(PKVM* vm, Var value) { - vm->fiber->self = value; -} - -static inline Var _getSelf(PKVM* vm) { - return vm->fiber->self; -} - static void _ctorNull(PKVM* vm) { - _setSelf(vm, VAR_NULL); + RET(VAR_NULL); } static void _ctorBool(PKVM* vm) { - _setSelf(vm, toBool(ARG(1))); + RET(toBool(ARG(1))); } static void _ctorNumber(PKVM* vm) { double value; if (!validateNumeric(vm, ARG(1), &value, "Argument 1")) return; - _setSelf(vm, VAR_NUM(value)); + RET(VAR_NUM(value)); } static void _ctorString(PKVM* vm) { if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return; if (ARGC == 0) { - _setSelf(vm, VAR_OBJ(newStringLength(vm, NULL, 0))); + RET(VAR_OBJ(newStringLength(vm, NULL, 0))); return; } - _setSelf(vm, VAR_OBJ(toString(vm, ARG(1)))); + RET(VAR_OBJ(toString(vm, ARG(1)))); } static void _ctorList(PKVM* vm) { @@ -1283,11 +1202,11 @@ static void _ctorList(PKVM* vm) { listAppend(vm, list, ARG(i + 1)); } vmPopTempRef(vm); // list. - _setSelf(vm, VAR_OBJ(list)); + RET(VAR_OBJ(list)); } static void _ctorMap(PKVM* vm) { - _setSelf(vm, VAR_OBJ(newMap(vm))); + RET(VAR_OBJ(newMap(vm))); } static void _ctorRange(PKVM* vm) { @@ -1295,26 +1214,72 @@ static void _ctorRange(PKVM* vm) { if (!validateNumeric(vm, ARG(1), &from, "Argument 1")) return; if (!validateNumeric(vm, ARG(2), &to, "Argument 2")) return; - _setSelf(vm, VAR_OBJ(newRange(vm, from, to))); + RET(VAR_OBJ(newRange(vm, from, to))); } static void _ctorFiber(PKVM* vm) { Closure* closure; if (!validateArgClosure(vm, 1, &closure)) return; - _setSelf(vm, VAR_OBJ(newFiber(vm, closure))); + RET(VAR_OBJ(newFiber(vm, closure))); } /*****************************************************************************/ /* BUILTIN CLASS METHODS */ /*****************************************************************************/ -static void _listAppend(PKVM* vm) { - Var self = _getSelf(vm); - ASSERT(IS_OBJ_TYPE(self, OBJ_LIST), OOPS); - listAppend(vm, ((List*)AS_OBJ(self)), ARG(1)); - RET(self); +#define SELF (vm->fiber->self) + +DEF(_listAppend, + "List.append(value:var) -> List\n" + "Append the [value] to the list and return the list.") { + + ASSERT(IS_OBJ_TYPE(SELF, OBJ_LIST), OOPS); + + listAppend(vm, ((List*)AS_OBJ(SELF)), ARG(1)); + RET(SELF); } +DEF(_fiberRun, + "Fiber.run(...) -> var\n" + "Runs the fiber's function with the provided arguments and returns it's " + "return value or the yielded value if it's yielded.") { + + ASSERT(IS_OBJ_TYPE(SELF, OBJ_FIBER), OOPS); + Fiber* self = (Fiber*)AS_OBJ(SELF); + + // Buffer of argument to call vmPrepareFiber(). + Var* args[MAX_ARGC]; + + for (int i = 0; i < ARGC; i++) { + args[i] = &ARG(i + 1); + } + + // Switch fiber and start execution. + if (vmPrepareFiber(vm, self, ARGC, args)) { + self->state = FIBER_RUNNING; + } +} + +DEF(_fiberResume, + "Fiber.resume() -> var\n" + "Resumes a yielded function from a previous call of fiber_run() function. " + "Return it's return value or the yielded value if it's yielded.") { + + ASSERT(IS_OBJ_TYPE(SELF, OBJ_FIBER), OOPS); + Fiber* self = (Fiber*)AS_OBJ(SELF); + + if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return; + + Var value = (ARGC == 1) ? ARG(1) : VAR_NULL; + + // Switch fiber and resume execution. + if (vmSwitchFiber(vm, self, &value)) { + self->state = FIBER_RUNNING; + } +} + +#undef SELF + /*****************************************************************************/ /* BUILTIN CLASS INITIALIZATION */ /*****************************************************************************/ @@ -1349,24 +1314,29 @@ static void initializePrimitiveClasses(PKVM* vm) { #undef ADD_CTOR -#define ADD_METHOD(type, name, ptr, arity_) \ - do { \ - Function* fn = newFunction(vm, name, (int)strlen(name), \ - NULL, true, NULL, NULL); \ - fn->native = ptr; \ - fn->arity = arity_; \ - vmPushTempRef(vm, &fn->_super); /* fn. */ \ - pkClosureBufferWrite(&vm->builtin_classes[type]->methods, \ - vm, newClosure(vm, fn)); \ - vmPopTempRef(vm); /* fn. */ \ +#define ADD_METHOD(type, name, ptr, arity_) \ + do { \ + Function* fn = newFunction(vm, name, (int)strlen(name), \ + NULL, true, DOCSTRING(ptr), NULL); \ + fn->native = ptr; \ + fn->arity = arity_; \ + vmPushTempRef(vm, &fn->_super); /* fn. */ \ + pkClosureBufferWrite(&vm->builtin_classes[type]->methods, \ + vm, newClosure(vm, fn)); \ + vmPopTempRef(vm); /* fn. */ \ } while (false) - ADD_METHOD(PK_LIST, "append", _listAppend, 1); + ADD_METHOD(PK_LIST, "append", _listAppend, 1); + ADD_METHOD(PK_FIBER, "run", _fiberRun, -1); + ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1); #undef ADD_METHOD - } +#undef IS_NUM_BYTE +#undef DOCSTRING +#undef DEF + /*****************************************************************************/ /* OPERATORS */ /*****************************************************************************/ diff --git a/src/pk_vm.c b/src/pk_vm.c index 2b9847a..a6242f3 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -1087,13 +1087,30 @@ L_do_call: closure = (const Closure*)AS_OBJ(callable); } else if (IS_OBJ_TYPE(callable, OBJ_CLASS)) { - closure = (const Closure*)((Class*)AS_OBJ(callable))->ctor; + Class* cls = (Class*)AS_OBJ(callable); + + // Allocate / create a new self before calling constructor on it. + fiber->self = preConstructSelf(vm, cls); + CHECK_ERROR(); + + closure = (const Closure*)(cls)->ctor; + + // No constructor is defined on the class. Just return self. + if (closure == NULL) { + if (argc != 0) { + String* msg = stringFormat(vm, "Expected exactly 0 argument(s)."); + RUNTIME_ERROR(msg); + } + + *fiber->ret = fiber->self; + fiber->self = VAR_UNDEFINED; + DISPATCH(); + } } else { RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a callable to " "call, instead got", varTypeName(callable), toString(vm, callable))); - DISPATCH(); } // If we reached here it's a valid callable. diff --git a/tests/lang/fibers.pk b/tests/lang/fibers.pk index 0a6a79a..25f6435 100644 --- a/tests/lang/fibers.pk +++ b/tests/lang/fibers.pk @@ -3,10 +3,8 @@ def f0() yield("yield value") end -import _Fiber - -fiber = _Fiber.new(f0) -yield_value = _Fiber.run(fiber) +fiber = Fiber(f0) +yield_value = fiber.run() assert(yield_value == "yield value") assert(!fiber.is_done) @@ -14,9 +12,10 @@ def f1() assert(yield("y") == "r") yield() end -fiber = _Fiber.new(f1) -assert(_Fiber.run(fiber) == "y") -_Fiber.resume(fiber, "r") + +fiber = Fiber(f1) +assert(fiber.run() == "y") +fiber.resume("r") assert(!fiber.is_done) def f2(p1, p2, p3) @@ -27,11 +26,11 @@ def f2(p1, p2, p3) return p1 + p2 * p3 end -fiber = _Fiber.new(f2) -p3 = _Fiber.run(fiber, 1, 2, 3); assert(p3 == 3) -p2 = _Fiber.resume(fiber, 'r1'); assert(p2 == 2) -p1 = _Fiber.resume(fiber, 'r2'); assert(p1 == 1) -pa = _Fiber.resume(fiber, 'r3'); assert(pa == 7) +fiber = Fiber(f2) +p3 = fiber.run(1, 2, 3); assert(p3 == 3) +p2 = fiber.resume('r1'); assert(p2 == 2) +p1 = fiber.resume('r2'); assert(p1 == 1) +pa = fiber.resume('r3'); assert(pa == 7) assert(fiber.is_done) # If we got here, that means all test were passed. From 179026294dde432d70f94787fdbf76ee566871d0 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Thu, 21 Apr 2022 17:23:03 +0530 Subject: [PATCH 8/9] class compilation implementation --- src/pk_compiler.c | 135 +++++++++++++++++++++++++++++++++++++------- src/pk_core.c | 65 ++++++++++----------- src/pk_value.c | 33 +++++------ src/pk_value.h | 7 ++- src/pk_vm.c | 7 ++- tests/lang/class.pk | 38 ++++++++++--- 6 files changed, 203 insertions(+), 82 deletions(-) diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 8fd9994..4b38e0f 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -125,6 +125,8 @@ typedef enum { TK_NOT, // not / ! TK_TRUE, // true TK_FALSE, // false + TK_SELF, // self + // TODO: TK_SUPER TK_DO, // do TK_THEN, // then @@ -186,6 +188,7 @@ static _Keyword _keywords[] = { { "not", 3, TK_NOT }, { "true", 4, TK_TRUE }, { "false", 5, TK_FALSE }, + { "self", 4, TK_SELF }, { "do", 2, TK_DO }, { "then", 4, TK_THEN }, { "while", 5, TK_WHILE }, @@ -241,6 +244,14 @@ typedef enum { DEPTH_LOCAL, //< Local scope. Increase with inner scope. } Depth; +typedef enum { + FUNC_MAIN, // The body function of the script. + FUNC_TOPLEVEL, + FUNC_LITERAL, + FUNC_METHOD, + FUNC_CONSTRUCTOR, +} FuncType; + typedef struct { const char* name; //< Directly points into the source string. uint32_t length; //< Length of the name. @@ -314,6 +325,9 @@ typedef struct sUpvalueInfo { typedef struct sFunc { + // Type of the current function. + FuncType type; + // Scope of the function. -2 for module body function, -1 for top level // function and literal functions will have the scope where it declared. int depth; @@ -396,8 +410,9 @@ typedef struct sParser { ForwardName forwards[MAX_FORWARD_NAMES]; int forwards_count; - bool repl_mode; //< True if compiling for REPL. - bool has_errors; //< True if any syntex error occurred at. + bool repl_mode; + bool parsing_class; + bool has_errors; bool need_more_lines; //< True if we need more lines in REPL mode. } Parser; @@ -507,6 +522,7 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler, parser->forwards_count = 0; parser->repl_mode = !!(compiler->options && compiler->options->repl_mode); + parser->parsing_class = false; parser->has_errors = false; parser->need_more_lines = false; } @@ -1444,7 +1460,7 @@ static void compilerChangeStack(Compiler* compiler, int num); // Forward declaration of grammar functions. static void parsePrecedence(Compiler* compiler, Precedence precedence); -static void compileFunction(Compiler* compiler, bool is_literal); +static int compileFunction(Compiler* compiler, FuncType fn_type); static void compileExpression(Compiler* compiler); static void exprLiteral(Compiler* compiler); @@ -1469,6 +1485,8 @@ static void exprSubscript(Compiler* compiler); // true, false, null, self. static void exprValue(Compiler* compiler); +static void exprSelf(Compiler* compiler); + #define NO_RULE { NULL, NULL, PREC_NONE } #define NO_INFIX PREC_NONE @@ -1534,6 +1552,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence /* TK_NOT */ { exprUnaryOp, NULL, PREC_UNARY }, /* TK_TRUE */ { exprValue, NULL, NO_INFIX }, /* TK_FALSE */ { exprValue, NULL, NO_INFIX }, + /* TK_FALSE */ { exprSelf, NULL, NO_INFIX }, /* TK_DO */ NO_RULE, /* TK_THEN */ NO_RULE, /* TK_WHILE */ NO_RULE, @@ -1698,7 +1717,7 @@ static void exprInterpolation(Compiler* compiler) { } static void exprFunction(Compiler* compiler) { - compileFunction(compiler, true); + compileFunction(compiler, FUNC_LITERAL); } static void exprName(Compiler* compiler) { @@ -2035,6 +2054,25 @@ static void exprValue(Compiler* compiler) { } } +static void exprSelf(Compiler* compiler) { + + if (compiler->func->type == FUNC_CONSTRUCTOR || + compiler->func->type == FUNC_METHOD) { + emitOpcode(compiler, OP_PUSH_SELF); + return; + } + + // If we reach here 'self' is used in either non method or a closure + // inside a method. + + if (!compiler->parser.parsing_class) { + parseError(compiler, "Invalid use of 'self'."); + } else { + // FIXME: + parseError(compiler, "TODO: Closures cannot capture 'self' for now."); + } +} + static void parsePrecedence(Compiler* compiler, Precedence precedence) { lexToken(&(compiler->parser)); GrammarFn prefix = getRule(compiler->parser.previous.type)->prefix; @@ -2204,7 +2242,8 @@ static void compilerExitBlock(Compiler* compiler) { } static void compilerPushFunc(Compiler* compiler, Func* fn, - Function* func) { + Function* func, FuncType type) { + fn->type = type; fn->outer_func = compiler->func; fn->local_count = 0; fn->stack_size = 0; @@ -2318,14 +2357,12 @@ static void compileStatement(Compiler* compiler); static void compileBlockBody(Compiler* compiler, BlockType type); // Compile a class and return it's index in the module's types buffer. -static void compileClass(Compiler* compiler) { +static int compileClass(Compiler* compiler) { ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS); - TODO; //< compileClass Function is in-compilete. - // Consume the name of the type. - consume(compiler, TK_NAME, "Expected a type name."); + consume(compiler, TK_NAME, "Expected a class name."); const char* name = compiler->parser.previous.start; int name_len = compiler->parser.previous.length; int name_line = compiler->parser.previous.line; @@ -2336,23 +2373,58 @@ static void compileClass(Compiler* compiler) { Class* cls = newClass(_vm, name, name_len, _vm->builtin_classes[PK_OBJECT], compiler->module, NULL, &cls_index); + vmPushTempRef(_vm, &cls->_super); // cls. + compiler->parser.parsing_class = true; // Check count exceeded. checkMaxConstantsReached(compiler, cls_index); - // Compile all the methods and constructors. - TODO; + skipNewLines(compiler); + while (!match(compiler, TK_END)) { + // At the top level the stack size should be 0, before and after compiling + // a top level statement, since there aren't any locals at the top level. + ASSERT(compiler->parser.has_errors || + compiler->func->stack_size == 0, OOPS); - consume(compiler, TK_END, "Expected 'end' after a class declaration end."); + consume(compiler, TK_DEF, "Expected method definition."); + int fn_index = compileFunction(compiler, FUNC_METHOD); + Var fn_var = compiler->module->constants.data[fn_index]; + ASSERT(IS_OBJ_TYPE(fn_var, OBJ_FUNC), OOPS); + + // TODO: check if the constructor or method already exists and report + // error. Make sure the error report line match the name token's line. + + Closure* method = newClosure(_vm, (Function*)AS_OBJ(fn_var)); + if (strcmp(method->fn->name, "_init") == 0) { + cls->ctor = method; + + } else { + vmPushTempRef(_vm, &method->_super); // method. + pkClosureBufferWrite(&cls->methods, _vm, method); + vmPopTempRef(_vm); // method. + } + + // At the top level the stack size should be 0, before and after compiling + // a top level statement, since there aren't any locals at the top level. + ASSERT(compiler->parser.has_errors || + compiler->func->stack_size == 0, OOPS); + + skipNewLines(compiler); + } + + compiler->parser.parsing_class = false; + vmPopTempRef(_vm); // cls. + + return cls_index; } // Compile a function and return it's index in the module's function buffer. -static void compileFunction(Compiler* compiler, bool is_literal) { +static int compileFunction(Compiler* compiler, FuncType fn_type) { const char* name; int name_length; - if (!is_literal) { + if (fn_type != FUNC_LITERAL) { consume(compiler, TK_NAME, "Expected a function name."); name = compiler->parser.previous.start; name_length = compiler->parser.previous.length; @@ -2367,7 +2439,7 @@ static void compileFunction(Compiler* compiler, bool is_literal) { compiler->module, false, NULL, &fn_index); checkMaxConstantsReached(compiler, fn_index); - if (!is_literal) { + if (fn_type != FUNC_LITERAL) { ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS); int name_line = compiler->parser.previous.line; int g_index = compilerAddVariable(compiler, name, name_length, name_line); @@ -2378,8 +2450,12 @@ static void compileFunction(Compiler* compiler, bool is_literal) { vmPopTempRef(compiler->parser.vm); // func. } + if (fn_type == FUNC_METHOD && strncmp(name, "_init", name_length) == 0) { + fn_type = FUNC_CONSTRUCTOR; + } + Func curr_fn; - compilerPushFunc(compiler, &curr_fn, func); + compilerPushFunc(compiler, &curr_fn, func, fn_type); int argc = 0; compilerEnterBlock(compiler); // Parameter depth. @@ -2422,6 +2498,11 @@ static void compileFunction(Compiler* compiler, bool is_literal) { compileBlockBody(compiler, BLOCK_FUNC); + if (fn_type == FUNC_CONSTRUCTOR) { + emitOpcode(compiler, OP_PUSH_SELF); + emitOpcode(compiler, OP_RETURN); + } + consume(compiler, TK_END, "Expected 'end' after function definition end."); compilerExitBlock(compiler); // Parameter depth. emitFunctionEnd(compiler); @@ -2439,7 +2520,7 @@ static void compileFunction(Compiler* compiler, bool is_literal) { // function of this function, and the bellow emit calls will write to the // outer function. If it's a literal function, we need to push a closure // of it on the stack. - if (is_literal) { + if (fn_type == FUNC_LITERAL) { emitOpcode(compiler, OP_PUSH_CLOSURE); emitShort(compiler, fn_index); @@ -2449,6 +2530,8 @@ static void compileFunction(Compiler* compiler, bool is_literal) { emitByte(compiler, curr_fn.upvalues[i].index); } } + + return fn_index; } // Finish a block body. @@ -3013,10 +3096,22 @@ static void compileStatement(Compiler* compiler) { } if (matchEndStatement(compiler)) { - emitOpcode(compiler, OP_PUSH_NULL); + + // Constructors will return self. + if (compiler->func->type == FUNC_CONSTRUCTOR) { + emitOpcode(compiler, OP_PUSH_SELF); + } else { + emitOpcode(compiler, OP_PUSH_NULL); + } + emitOpcode(compiler, OP_RETURN); } else { + + if (compiler->func->type == FUNC_CONSTRUCTOR) { + parseError(compiler, "Cannor 'return' a value from constructor."); + } + compileExpression(compiler); //< Return value is at stack top. // If the last expression parsed with compileExpression() is a call @@ -3076,7 +3171,7 @@ static void compileTopLevelStatement(Compiler* compiler) { compileClass(compiler); } else if (match(compiler, TK_DEF)) { - compileFunction(compiler, false); + compileFunction(compiler, FUNC_TOPLEVEL); } else if (match(compiler, TK_FROM)) { compileFromImport(compiler); @@ -3130,7 +3225,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source, uint32_t globals_count = module->globals.count; Func curr_fn; - compilerPushFunc(compiler, &curr_fn, module->body->fn); + compilerPushFunc(compiler, &curr_fn, module->body->fn, FUNC_MAIN); // Lex initial tokens. current <-- next. lexToken(&(compiler->parser)); diff --git a/src/pk_core.c b/src/pk_core.c index 7d200da..9fe0306 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -1289,8 +1289,10 @@ static void initializePrimitiveClasses(PKVM* vm) { Class* super = NULL; if (i != 0) super = vm->builtin_classes[PK_OBJECT]; const char* name = getPkVarTypeName((PkVarType)i); - vm->builtin_classes[i] = newClass(vm, name, (int)strlen(name), - super, NULL, NULL, NULL); + Class* cls = newClass(vm, name, (int)strlen(name), + super, NULL, NULL, NULL); + vm->builtin_classes[i] = cls; + cls->class_of = (PkVarType)i; } #define ADD_CTOR(type, name, ptr, arity_) \ @@ -1347,46 +1349,41 @@ Var preConstructSelf(PKVM* vm, Class* cls) { VM_SET_ERROR(vm, newString(vm, \ "Class '" type_name "' cannot be instanciated.")) - for (int i = 0; i < PK_INSTANCE; i++) { - if (vm->builtin_classes[i] == cls) { + switch (cls->class_of) { + case PK_OBJECT: + NO_INSTANCE("Object"); + return VAR_NULL; - switch ((PkVarType)i) { - case PK_OBJECT: - NO_INSTANCE("Object"); - return VAR_NULL; + case PK_NULL: + case PK_BOOL: + case PK_NUMBER: + case PK_STRING: + case PK_LIST: + case PK_MAP: + case PK_RANGE: + return VAR_NULL; // Constructor will override the null. - case PK_NULL: - case PK_BOOL: - case PK_NUMBER: - case PK_STRING: - case PK_LIST: - case PK_MAP: - case PK_RANGE: - return VAR_NULL; // Constructor will override the null. + case PK_MODULE: + NO_INSTANCE("Module"); + return VAR_NULL; - case PK_MODULE: - NO_INSTANCE("Module"); - return VAR_NULL; + case PK_CLOSURE: + NO_INSTANCE("Closure"); + return VAR_NULL; - case PK_CLOSURE: - NO_INSTANCE("Closure"); - return VAR_NULL; + case PK_FIBER: + return VAR_NULL; - case PK_FIBER: - return VAR_NULL; + case PK_CLASS: + NO_INSTANCE("Class"); + return VAR_NULL; - case PK_CLASS: - NO_INSTANCE("Class"); - return VAR_NULL; - - case PK_INSTANCE: - UNREACHABLE(); - return VAR_NULL; - } - } + case PK_INSTANCE: + return VAR_OBJ(newInstance(vm, cls)); } - return VAR_OBJ(newInstance(vm, cls)); + UNREACHABLE(); + return VAR_NULL; } Class* getClass(PKVM* vm, Var instance) { diff --git a/src/pk_value.c b/src/pk_value.c index dba3176..a07be49 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -288,7 +288,7 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { case OBJ_INST: { Instance* inst = (Instance*)obj; - markObject(vm, &inst->attribs._super); + markObject(vm, &inst->attribs->_super); markObject(vm, &inst->cls->_super); vm->bytes_allocated += sizeof(Instance); } break; @@ -515,6 +515,7 @@ Class* newClass(PKVM* vm, const char* name, int length, pkClosureBufferInit(&cls->methods); + cls->class_of = PK_INSTANCE; cls->owner = NULL; cls->super_class = super; cls->docstring = NULL; @@ -538,28 +539,23 @@ Class* newClass(PKVM* vm, const char* name, int length, Instance* newInstance(PKVM* vm, Class* cls) { -#ifdef DEBUG - bool _builtin_class = false; - for (int i = 0; i < PK_INSTANCE; i++) { - if (vm->builtin_classes[i] == cls) { - _builtin_class = true; - break; - } - } - ASSERT(!_builtin_class, "Cannot create an instace of builtin class " - "with newInstance() function."); -#endif // DEBUG + ASSERT(cls->class_of == PK_INSTANCE, "Cannot create an instace of builtin " + "class with newInstance() function."); Instance* inst = ALLOCATE(vm, Instance); varInitObject(&inst->_super, vm, OBJ_INST); + vmPushTempRef(vm, &inst->_super); // inst. + inst->cls = cls; + inst->attribs = newMap(vm); + if (cls->new_fn != NULL) { - vmPushTempRef(vm, &inst->_super); // inst. inst->native = cls->new_fn(); - vmPopTempRef(vm); // inst. } else { inst->native = NULL; } + + vmPopTempRef(vm); // inst. return inst; } @@ -1170,10 +1166,10 @@ bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value) { ASSERT((inst != NULL) && (attrib != NULL) && (value != NULL), OOPS); if (inst->native != NULL) { + TODO; } - TODO; - Var value_ = mapGet(&inst->attribs, VAR_OBJ(attrib)); + Var value_ = mapGet(inst->attribs, VAR_OBJ(attrib)); if (IS_UNDEF(value_)) return false; *value = value_; @@ -1194,7 +1190,7 @@ bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value) { return true; } - mapSet(vm, &inst->attribs, VAR_OBJ(attrib), value); + mapSet(vm, inst->attribs, VAR_OBJ(attrib), value); return true; } @@ -1591,9 +1587,10 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff, { const Instance* inst = (const Instance*)obj; pkByteBufferWrite(buff, vm, '['); + pkByteBufferWrite(buff, vm, '\''); pkByteBufferAddString(buff, vm, inst->cls->name->data, inst->cls->name->length); - pkByteBufferWrite(buff, vm, ':'); + pkByteBufferAddString(buff, vm, "' instance at ", 14); char buff_addr[STR_HEX_BUFF_SIZE]; char* ptr = (char*)buff_addr; diff --git a/src/pk_value.h b/src/pk_value.h index 20d128f..efd4f3f 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -501,6 +501,11 @@ struct Class { // entry in it's owner module's constant pool. const char* docstring; + // For builtin type it'll be it's enum (ex: PK_STRING, PK_NUMBER, ...) for + // every other classes it'll be PK_INSTANCE to indicate that it's not a + // builtin type's class. + PkVarType class_of; + Closure* ctor; //< The constructor function. // A buffer of methods of the class. @@ -531,7 +536,7 @@ struct Instance { void* native; // Dynamic attributes of an instance. - Map attribs; + Map* attribs; }; /*****************************************************************************/ diff --git a/src/pk_vm.c b/src/pk_vm.c index a6242f3..a9ab792 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -551,7 +551,7 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) { frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; - // Eat the self. + // Capture self. frame->self = vm->fiber->self; vm->fiber->self = VAR_UNDEFINED; } @@ -567,7 +567,10 @@ static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { CallFrame* frame = fb->frames + fb->frame_count - 1; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; - frame->self = VAR_UNDEFINED; + + // Capture self. + frame->self = vm->fiber->self; + vm->fiber->self = VAR_UNDEFINED; ASSERT(*frame->rbp == VAR_NULL, OOPS); diff --git a/tests/lang/class.pk b/tests/lang/class.pk index 13e5e7c..952b2ba 100644 --- a/tests/lang/class.pk +++ b/tests/lang/class.pk @@ -1,9 +1,33 @@ - - -## Note that classes are being implemented and temproarly -## the classes cannot be compiled. - - - + +class Vec2 + def _init(x, y) + self.x = x + self.y = y + end + + def add(other) + return Vec2(self.x + other.x, + self.y + other.y) + end + + ## Note that operator overloading / friend functions + ## haven't implemented at this point (to_string won't actually + ## override it). + def to_string + return "[${self.x}, ${self.y}]" + end + +end + +v1 = Vec2(1, 2); assert(v1.x == 1 and v1.y == 2) +print("v1 = ${v1.to_string()}") + +v2 = Vec2(3, 4); assert(v2.x == 3 and v2.y == 4) +print("v2 = ${v2.to_string()}") + +v3 = v1.add(v2); assert(v3.x == 4 and v3.y == 6) +print("v3 = ${v3.to_string()}") + +print('ALL TESTS PASSED') From 2fe579d9cc6cb062fc1487796a373e99e395e211 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Thu, 21 Apr 2022 21:37:38 +0530 Subject: [PATCH 9/9] math module moved to cli/modules --- cli/all.c | 1 + cli/modules/modules.h | 12 ++ cli/modules/std_io.c | 8 +- cli/modules/std_math.c | 218 ++++++++++++++++++++++++++ cli/modules/std_path.c | 28 ++-- src/pk_core.c | 298 ++++++------------------------------ src/pk_value.c | 27 +--- tests/examples/brainfuck.pk | 2 +- tests/examples/pi.pk | 6 +- tests/lang/basics.pk | 8 +- tests/lang/core.pk | 58 ------- tests/modules/math.pk | 64 ++++++++ tests/native/README.md | 4 +- tests/native/example1.c | 37 ++--- tests/native/example2.c | 222 +++++++-------------------- tests/random/linked_list.pk | 67 ++++++++ tests/random/lisp_eval.pk | 4 +- tests/tests.py | 5 + 18 files changed, 522 insertions(+), 547 deletions(-) create mode 100644 cli/modules/std_math.c create mode 100644 tests/modules/math.pk create mode 100644 tests/random/linked_list.pk diff --git a/cli/all.c b/cli/all.c index 1b8cae7..294fd44 100644 --- a/cli/all.c +++ b/cli/all.c @@ -15,6 +15,7 @@ #include "modules/std_io.c" #include "modules/std_path.c" +#include "modules/std_math.c" /*****************************************************************************/ /* THIRDPARTY SOURCES */ diff --git a/cli/modules/modules.h b/cli/modules/modules.h index d81854b..0088f4a 100644 --- a/cli/modules/modules.h +++ b/cli/modules/modules.h @@ -16,12 +16,14 @@ void registerModuleIO(PKVM* vm); void registerModulePath(PKVM* vm); +void registerModuleMath(PKVM* vm); // Registers all the cli modules. #define REGISTER_ALL_MODULES(vm) \ do { \ registerModuleIO(vm); \ registerModulePath(vm); \ + registerModuleMath(vm); \ } while (false) /*****************************************************************************/ @@ -35,6 +37,16 @@ void registerModulePath(PKVM* vm); // callback. #define FREE_OBJ(ptr) free(ptr) +// Returns the docstring of the function, which is a static const char* defined +// just above the function by the DEF() macro below. +#define DOCSTRING(fn) __doc_##fn + +// A macro to declare a function, with docstring, which is defined as +// ___doc_ = docstring; That'll used to generate function help text. +#define DEF(fn, docstring) \ + static const char* DOCSTRING(fn) = docstring; \ + static void fn(PKVM* vm) + /*****************************************************************************/ /* SHARED FUNCTIONS */ /*****************************************************************************/ diff --git a/cli/modules/std_io.c b/cli/modules/std_io.c index e2c6402..84030c5 100644 --- a/cli/modules/std_io.c +++ b/cli/modules/std_io.c @@ -56,7 +56,7 @@ void _deleteFile(void* ptr) { /* FILE MODULE FUNCTIONS */ /*****************************************************************************/ -static void _fileOpen(PKVM* vm) { +DEF(_fileOpen, "") { int argc = pkGetArgc(vm); if (!pkCheckArgcRange(vm, argc, 1, 2)) return; @@ -103,7 +103,7 @@ static void _fileOpen(PKVM* vm) { } } -static void _fileRead(PKVM* vm) { +DEF(_fileRead, "") { // This TODO is just a blockade from running the bellow code, complete the // native interface and test before removing it. TODO; @@ -126,7 +126,7 @@ static void _fileRead(PKVM* vm) { pkReturnString(vm, (const char*)buff); } -static void _fileWrite(PKVM* vm) { +DEF(_fileWrite, "") { // This TODO is just a blockade from running the bellow code, complete the // native interface and test before removing it. TODO; @@ -148,7 +148,7 @@ static void _fileWrite(PKVM* vm) { fwrite(text, sizeof(char), (size_t)length, file->fp); } -static void _fileClose(PKVM* vm) { +DEF(_fileClose, "") { // This TODO is just a blockade from running the bellow code, complete the // native interface and test before removing it. TODO; diff --git a/cli/modules/std_math.c b/cli/modules/std_math.c new file mode 100644 index 0000000..d27ae20 --- /dev/null +++ b/cli/modules/std_math.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2020-2022 Thakee Nathees + * Copyright (c) 2021-2022 Pocketlang Contributors + * Distributed Under The MIT License + */ + +#include "modules.h" + +#include + +// M_PI is non standard. The macro _USE_MATH_DEFINES defining before importing +// will define the constants for MSVC. But for a portable solution, +// we're defining it ourselves if it isn't already. +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +DEF(stdMathFloor, + "floor(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, floor(num)); +} + +DEF(stdMathCeil, + "ceil(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, ceil(num)); +} + +DEF(stdMathPow, + "pow(value:num) -> num\n") { + + double num, ex; + if (!pkGetArgNumber(vm, 1, &num)) return; + if (!pkGetArgNumber(vm, 2, &ex)) return; + pkReturnNumber(vm, pow(num, ex)); +} + +DEF(stdMathSqrt, + "sqrt(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, sqrt(num)); +} + +DEF(stdMathAbs, + "abs(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + if (num < 0) num = -num; + pkReturnNumber(vm, num); +} + +DEF(stdMathSign, + "sign(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + if (num < 0) num = -1; + else if (num > 0) num = +1; + else num = 0; + pkReturnNumber(vm, num); +} + +DEF(stdMathSine, + "sin(rad:num) -> num\n" + "Return the sine value of the argument [rad] which is an angle expressed " + "in radians.") { + + double rad; + if (!pkGetArgNumber(vm, 1, &rad)) return; + pkReturnNumber(vm, sin(rad)); +} + +DEF(stdMathCosine, + "cos(rad:num) -> num\n" + "Return the cosine value of the argument [rad] which is an angle expressed " + "in radians.") { + + double rad; + if (!pkGetArgNumber(vm, 1, &rad)) return; + pkReturnNumber(vm, cos(rad)); +} + +DEF(stdMathTangent, + "tan(rad:num) -> num\n" + "Return the tangent value of the argument [rad] which is an angle expressed " + "in radians.") { + + double rad; + if (!pkGetArgNumber(vm, 1, &rad)) return; + pkReturnNumber(vm, tan(rad)); +} + +DEF(stdMathSinh, + "sinh(val) -> val\n" + "Return the hyperbolic sine value of the argument [val].") { + + double val; + if (!pkGetArgNumber(vm, 1, &val)) return; + pkReturnNumber(vm, sinh(val)); +} + +DEF(stdMathCosh, + "cosh(val) -> val\n" + "Return the hyperbolic cosine value of the argument [val].") { + + double val; + if (!pkGetArgNumber(vm, 1, &val)) return; + pkReturnNumber(vm, cosh(val)); +} + +DEF(stdMathTanh, + "tanh(val) -> val\n" + "Return the hyperbolic tangent value of the argument [val].") { + + double val; + if (!pkGetArgNumber(vm, 1, &val)) return; + pkReturnNumber(vm, tanh(val)); +} + +DEF(stdMathArcSine, + "asin(num) -> num\n" + "Return the arcsine value of the argument [num] which is an angle " + "expressed in radians.") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + + if (num < -1 || 1 < num) { + pkSetRuntimeError(vm, "Argument should be between -1 and +1"); + } + + pkReturnNumber(vm, asin(num)); +} + +DEF(stdMathArcCosine, + "acos(num) -> num\n" + "Return the arc cosine value of the argument [num] which is " + "an angle expressed in radians.") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + + if (num < -1 || 1 < num) { + pkSetRuntimeError(vm, "Argument should be between -1 and +1"); + } + + pkReturnNumber(vm, acos(num)); +} + +DEF(stdMathArcTangent, + "atan(num) -> num\n" + "Return the arc tangent value of the argument [num] which is " + "an angle expressed in radians.") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, atan(num)); +} + +DEF(stdMathLog10, + "log10(value:num) -> num\n" + "Return the logarithm to base 10 of argument [value]") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, log10(num)); +} + +DEF(stdMathRound, + "round(value:num) -> num\n" + "Round to nearest integer, away from zero and return the number.") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, round(num)); +} + +void registerModuleMath(PKVM* vm) { + + PkHandle* math = pkNewModule(vm, "math"); + + pkModuleAddFunction(vm, math, "floor", stdMathFloor, 1); + pkModuleAddFunction(vm, math, "ceil", stdMathCeil, 1); + pkModuleAddFunction(vm, math, "pow", stdMathPow, 2); + pkModuleAddFunction(vm, math, "sqrt", stdMathSqrt, 1); + pkModuleAddFunction(vm, math, "abs", stdMathAbs, 1); + pkModuleAddFunction(vm, math, "sign", stdMathSign, 1); + pkModuleAddFunction(vm, math, "sin", stdMathSine, 1); + pkModuleAddFunction(vm, math, "cos", stdMathCosine, 1); + pkModuleAddFunction(vm, math, "tan", stdMathTangent, 1); + pkModuleAddFunction(vm, math, "sinh", stdMathSinh, 1); + pkModuleAddFunction(vm, math, "cosh", stdMathCosh, 1); + pkModuleAddFunction(vm, math, "tanh", stdMathTanh, 1); + pkModuleAddFunction(vm, math, "asin", stdMathArcSine, 1); + pkModuleAddFunction(vm, math, "acos", stdMathArcCosine, 1); + pkModuleAddFunction(vm, math, "atan", stdMathArcTangent, 1); + pkModuleAddFunction(vm, math, "log10", stdMathLog10, 1); + pkModuleAddFunction(vm, math, "round", stdMathRound, 1); + + // FIXME: + // Refactor native type interface and add PI as a global to the module. + // + // Note that currently it's mutable (since it's a global variable, not + // constant and pocketlang doesn't support constant) so the user shouldn't + // modify the PI, like in python. + //pkModuleAddGlobal(vm, math, "PI", Handle-Of-PI); + + pkRegisterModule(vm, math); + pkReleaseHandle(vm, math); +} diff --git a/cli/modules/std_path.c b/cli/modules/std_path.c index 52e16ba..4f62a4c 100644 --- a/cli/modules/std_path.c +++ b/cli/modules/std_path.c @@ -4,6 +4,8 @@ * Distributed Under The MIT License */ +#include "modules.h" + #include "thirdparty/cwalk/cwalk.h" #if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) #include "thirdparty/dirent/dirent.h" @@ -90,13 +92,13 @@ static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) { /* PATH MODULE FUNCTIONS */ /*****************************************************************************/ -static void _pathSetStyleUnix(PKVM* vm) { +DEF(_pathSetStyleUnix, "") { bool value; if (!pkGetArgBool(vm, 1, &value)) return; cwk_path_set_style((value) ? CWK_STYLE_UNIX : CWK_STYLE_WINDOWS); } -static void _pathGetCWD(PKVM* vm) { +DEF(_pathGetCWD, "") { char cwd[FILENAME_MAX]; if (get_cwd(cwd, sizeof(cwd)) == NULL) { // TODO: Handle error. @@ -104,7 +106,7 @@ static void _pathGetCWD(PKVM* vm) { pkReturnString(vm, cwd); } -static void _pathAbspath(PKVM* vm) { +DEF(_pathAbspath, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -113,7 +115,7 @@ static void _pathAbspath(PKVM* vm) { pkReturnStringLength(vm, abspath, len); } -static void _pathRelpath(PKVM* vm) { +DEF(_pathRelpath, "") { const char* from, * path; if (!pkGetArgString(vm, 1, &from, NULL)) return; if (!pkGetArgString(vm, 2, &path, NULL)) return; @@ -130,7 +132,7 @@ static void _pathRelpath(PKVM* vm) { pkReturnStringLength(vm, result, len); } -static void _pathJoin(PKVM* vm) { +DEF(_pathJoin, "") { const char* paths[MAX_JOIN_PATHS + 1]; // +1 for NULL. int argc = pkGetArgc(vm); @@ -150,7 +152,7 @@ static void _pathJoin(PKVM* vm) { pkReturnStringLength(vm, result, len); } -static void _pathNormalize(PKVM* vm) { +DEF(_pathNormalize, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -159,7 +161,7 @@ static void _pathNormalize(PKVM* vm) { pkReturnStringLength(vm, result, len); } -static void _pathBaseName(PKVM* vm) { +DEF(_pathBaseName, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -169,7 +171,7 @@ static void _pathBaseName(PKVM* vm) { pkReturnString(vm, base_name); } -static void _pathDirName(PKVM* vm) { +DEF(_pathDirName, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -178,14 +180,14 @@ static void _pathDirName(PKVM* vm) { pkReturnStringLength(vm, path, length); } -static void _pathIsPathAbs(PKVM* vm) { +DEF(_pathIsPathAbs, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; pkReturnBool(vm, cwk_path_is_absolute(path)); } -static void _pathGetExtension(PKVM* vm) { +DEF(_pathGetExtension, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -198,19 +200,19 @@ static void _pathGetExtension(PKVM* vm) { } } -static void _pathExists(PKVM* vm) { +DEF(_pathExists, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; pkReturnBool(vm, pathIsExists(path)); } -static void _pathIsFile(PKVM* vm) { +DEF(_pathIsFile, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; pkReturnBool(vm, pathIsFileExists(path)); } -static void _pathIsDir(PKVM* vm) { +DEF(_pathIsDir, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; pkReturnBool(vm, pathIsDirectoryExists(path)); diff --git a/src/pk_core.c b/src/pk_core.c index 9fe0306..cf94153 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -15,13 +15,6 @@ #include "pk_utils.h" #include "pk_vm.h" -// M_PI is non standard. The macro _USE_MATH_DEFINES defining before importing -// will define the constants for MSVC. But for a portable solution, -// we're defining it ourselves if it isn't already. -#ifndef M_PI - #define M_PI 3.14159265358979323846 -#endif - // Returns the docstring of the function, which is a static const char* defined // just above the function by the DEF() macro below. #define DOCSTRING(fn) _pk_doc_##fn @@ -109,9 +102,16 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls, // won't be garbage collected (class handle has reference to the module). Closure* method = newClosure(vm, fn); - vmPushTempRef(vm, &method->_super); // method. - pkClosureBufferWrite(&class_->methods, vm, method); - vmPopTempRef(vm); // method. + + // FIXME: name "_init" is literal everywhere. + if (strcmp(name, "_init") == 0) { + class_->ctor = method; + + } else { + vmPushTempRef(vm, &method->_super); // method. + pkClosureBufferWrite(&class_->methods, vm, method); + vmPopTempRef(vm); // method. + } } void* pkGetSelf(const PKVM* vm) { @@ -535,7 +535,7 @@ DEF(coreAssert, DEF(coreBin, "bin(value:num) -> string\n" - "Returns as a binary value string with '0x' prefix.") { + "Returns as a binary value string with '0b' prefix.") { int64_t value; if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return; @@ -613,6 +613,35 @@ DEF(coreToString, RET(VAR_OBJ(toString(vm, ARG(1)))); } +DEF(coreChr, + "chr(value:num) -> string\n" + "Returns the ASCII string value of the integer argument.") { + + int64_t num; + if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return; + + if (!IS_NUM_BYTE(num)) { + RET_ERR(newString(vm, "The number is not in a byte range.")); + } + + char c = (char)num; + RET(VAR_OBJ(newStringLength(vm, &c, 1))); +} + +DEF(coreOrd, + "ord(value:string) -> num\n" + "Returns integer value of the given ASCII character.") { + + String* c; + if (!validateArgString(vm, 1, &c)) return; + if (c->length != 1) { + RET_ERR(newString(vm, "Expected a string of length 1.")); + + } else { + RET(VAR_NUM((double)c->data[0])); + } +} + DEF(corePrint, "print(...) -> void\n" "Write each argument as space seperated, to the stdout and ends with a " @@ -700,35 +729,6 @@ DEF(coreStrSub, RET(VAR_OBJ(newStringLength(vm, str->data + pos, (uint32_t)len))); } -DEF(coreStrChr, - "str_chr(value:num) -> string\n" - "Returns the ASCII string value of the integer argument.") { - - int64_t num; - if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return; - - if (!IS_NUM_BYTE(num)) { - RET_ERR(newString(vm, "The number is not in a byte range.")); - } - - char c = (char)num; - RET(VAR_OBJ(newStringLength(vm, &c, 1))); -} - -DEF(coreStrOrd, - "str_ord(value:string) -> num\n" - "Returns integer value of the given ASCII character.") { - - String* c; - if (!validateArgString(vm, 1, &c)) return; - if (c->length != 1) { - RET_ERR(newString(vm, "Expected a string of length 1.")); - - } else { - RET(VAR_NUM((double)c->data[0])); - } -} - // List functions. // --------------- @@ -806,14 +806,17 @@ static void initializeBuiltinFunctions(PKVM* vm) { INITIALIZE_BUILTIN_FN("hex", coreHex, 1); INITIALIZE_BUILTIN_FN("yield", coreYield, -1); INITIALIZE_BUILTIN_FN("to_string", coreToString, 1); + INITIALIZE_BUILTIN_FN("chr", coreChr, 1); + INITIALIZE_BUILTIN_FN("ord", coreOrd, 1); INITIALIZE_BUILTIN_FN("print", corePrint, -1); INITIALIZE_BUILTIN_FN("input", coreInput, -1); INITIALIZE_BUILTIN_FN("exit", coreExit, -1); + // FIXME: + // move this functions as methods. and make "append()" a builtin. + // String functions. INITIALIZE_BUILTIN_FN("str_sub", coreStrSub, 3); - INITIALIZE_BUILTIN_FN("str_chr", coreStrChr, 1); - INITIALIZE_BUILTIN_FN("str_ord", coreStrOrd, 1); // List functions. INITIALIZE_BUILTIN_FN("list_append", coreListAppend, 2); @@ -934,192 +937,6 @@ DEF(stdLangWrite, } } -// TODO: Move math to cli as it's not part of the pocketlang core. -// -// 'math' library methods. -// ----------------------- - -DEF(stdMathFloor, - "floor(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(floor(num))); -} - -DEF(stdMathCeil, - "ceil(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(ceil(num))); -} - -DEF(stdMathPow, - "pow(value:num) -> num\n") { - - double num, ex; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - if (!validateNumeric(vm, ARG(2), &ex, "Argument 2")) return; - RET(VAR_NUM(pow(num, ex))); -} - -DEF(stdMathSqrt, - "sqrt(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(sqrt(num))); -} - -DEF(stdMathAbs, - "abs(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - if (num < 0) num = -num; - RET(VAR_NUM(num)); -} - -DEF(stdMathSign, - "sign(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - if (num < 0) num = -1; - else if (num > 0) num = +1; - else num = 0; - RET(VAR_NUM(num)); -} - -DEF(stdMathHash, - "hash(value:var) -> num\n" - "Return the hash value of the variable, if it's not hashable it'll " - "return null.") { - - if (IS_OBJ(ARG(1))) { - if (!isObjectHashable(AS_OBJ(ARG(1))->type)) { - RET(VAR_NULL); - } - } - RET(VAR_NUM((double)varHashValue(ARG(1)))); -} - -DEF(stdMathSine, - "sin(rad:num) -> num\n" - "Return the sine value of the argument [rad] which is an angle expressed " - "in radians.") { - - double rad; - if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return; - RET(VAR_NUM(sin(rad))); -} - -DEF(stdMathCosine, - "cos(rad:num) -> num\n" - "Return the cosine value of the argument [rad] which is an angle expressed " - "in radians.") { - - double rad; - if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return; - RET(VAR_NUM(cos(rad))); -} - -DEF(stdMathTangent, - "tan(rad:num) -> num\n" - "Return the tangent value of the argument [rad] which is an angle expressed " - "in radians.") { - - double rad; - if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return; - RET(VAR_NUM(tan(rad))); -} - -DEF(stdMathSinh, - "sinh(val) -> val\n" - "Return the hyperbolic sine value of the argument [val].") { - - double val; - if (!validateNumeric(vm, ARG(1), &val, "Argument 1")) return; - RET(VAR_NUM(sinh(val))); -} - -DEF(stdMathCosh, - "cosh(val) -> val\n" - "Return the hyperbolic cosine value of the argument [val].") { - - double val; - if (!validateNumeric(vm, ARG(1), &val, "Argument 1")) return; - RET(VAR_NUM(cosh(val))); -} - -DEF(stdMathTanh, - "tanh(val) -> val\n" - "Return the hyperbolic tangent value of the argument [val].") { - - double val; - if (!validateNumeric(vm, ARG(1), &val, "Argument 1")) return; - RET(VAR_NUM(tanh(val))); -} - -DEF(stdMathArcSine, - "asin(num) -> num\n" - "Return the arcsine value of the argument [num] which is an angle " - "expressed in radians.") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - - if (num < -1 || 1 < num) { - RET_ERR(newString(vm, "Argument should be between -1 and +1")); - } - - RET(VAR_NUM(asin(num))); -} - -DEF(stdMathArcCosine, - "acos(num) -> num\n" - "Return the arc cosine value of the argument [num] which is " - "an angle expressed in radians.") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - - if (num < -1 || 1 < num) { - RET_ERR(newString(vm, "Argument should be between -1 and +1")); - } - - RET(VAR_NUM(acos(num))); -} - -DEF(stdMathArcTangent, - "atan(num) -> num\n" - "Return the arc tangent value of the argument [num] which is " - "an angle expressed in radians.") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(atan(num))); -} - -DEF(stdMathLog10, - "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 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))); -} - static void initializeCoreModules(PKVM* vm) { #define MODULE_ADD_FN(module, name, fn, argc) \ moduleAddFunctionInternal(vm, module, name, fn, argc, DOCSTRING(fn)) @@ -1139,31 +956,6 @@ static void initializeCoreModules(PKVM* vm) { MODULE_ADD_FN(lang, "debug_break", stdLangDebugBreak, 0); #endif - NEW_MODULE(math, "math"); - MODULE_ADD_FN(math, "floor", stdMathFloor, 1); - MODULE_ADD_FN(math, "ceil", stdMathCeil, 1); - MODULE_ADD_FN(math, "pow", stdMathPow, 2); - MODULE_ADD_FN(math, "sqrt", stdMathSqrt, 1); - MODULE_ADD_FN(math, "abs", stdMathAbs, 1); - MODULE_ADD_FN(math, "sign", stdMathSign, 1); - MODULE_ADD_FN(math, "hash", stdMathHash, 1); - MODULE_ADD_FN(math, "sin", stdMathSine, 1); - MODULE_ADD_FN(math, "cos", stdMathCosine, 1); - MODULE_ADD_FN(math, "tan", stdMathTangent, 1); - MODULE_ADD_FN(math, "sinh", stdMathSinh, 1); - MODULE_ADD_FN(math, "cosh", stdMathCosh, 1); - MODULE_ADD_FN(math, "tanh", stdMathTanh, 1); - MODULE_ADD_FN(math, "asin", stdMathArcSine, 1); - MODULE_ADD_FN(math, "acos", stdMathArcCosine, 1); - MODULE_ADD_FN(math, "atan", stdMathArcTangent, 1); - MODULE_ADD_FN(math, "log10", stdMathLog10, 1); - MODULE_ADD_FN(math, "round", stdMathRound, 1); - - // Note that currently it's mutable (since it's a global variable, not - // constant and pocketlang doesn't support constant) so the user shouldn't - // modify the PI, like in python. - moduleAddGlobal(vm, math, "PI", 2, VAR_NUM(M_PI)); - #undef MODULE_ADD_FN #undef NEW_MODULE } diff --git a/src/pk_value.c b/src/pk_value.c index a07be49..b388137 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -779,7 +779,8 @@ List* listJoin(PKVM* vm, List* l1, List* l2) { return list; } -// Return a hash value for the object. +// Return a hash value for the object. Only String and Range objects can be +// hashable. static uint32_t _hashObject(Object* obj) { ASSERT(isObjectHashable(obj->type), @@ -790,28 +791,10 @@ static uint32_t _hashObject(Object* obj) { case OBJ_STRING: return ((String*)obj)->hash; - case OBJ_LIST: - case OBJ_MAP: - goto L_unhashable; - - case OBJ_RANGE: - { + case OBJ_RANGE: { Range* range = (Range*)obj; return utilHashNumber(range->from) ^ utilHashNumber(range->to); } - - case OBJ_MODULE: - case OBJ_FUNC: - case OBJ_FIBER: - case OBJ_CLASS: - case OBJ_INST: - TODO; - UNREACHABLE(); - - default: - L_unhashable: - UNREACHABLE(); - break; } UNREACHABLE(); @@ -1346,8 +1329,8 @@ bool isValuesEqual(Var v1, Var v2) { } bool isObjectHashable(ObjectType type) { - // Only list and map are un-hashable. - return type != OBJ_LIST && type != OBJ_MAP; + // Only String and Range are hashable (since they're immutable). + return type == OBJ_STRING || type == OBJ_RANGE; } // This will prevent recursive list/map from crash when calling to_string, by diff --git a/tests/examples/brainfuck.pk b/tests/examples/brainfuck.pk index dd63727..0f35837 100644 --- a/tests/examples/brainfuck.pk +++ b/tests/examples/brainfuck.pk @@ -67,7 +67,7 @@ def execute(expr) ## output the byte at the data pointer. else if c == '.' - write(str_chr(mem[ptr])) + write(chr(mem[ptr])) else if c == ',' assert(false, "Currently input isn't supported in pocketlang.") diff --git a/tests/examples/pi.pk b/tests/examples/pi.pk index 3440632..b6e4ff2 100644 --- a/tests/examples/pi.pk +++ b/tests/examples/pi.pk @@ -3,7 +3,11 @@ ## ## PI/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - ... -from math import abs, PI +from math import abs + +## Temproarly we cannot register variables on native modules +## will be implemented soon. So defining the PI here. +PI = 3.14159265358979323846 pi_by_4 = 0; sign = -1 for i in 1..100000 diff --git a/tests/lang/basics.pk b/tests/lang/basics.pk index 89e43d1..f415e0f 100644 --- a/tests/lang/basics.pk +++ b/tests/lang/basics.pk @@ -37,11 +37,9 @@ assert(!('foo' in {'bar':'baz'})) ## Builtin functions tests. assert(to_string(42) == '42') -## Core module test. -import math -h1 = math.hash("testing"); h2 = math.hash("test" + "ing") -assert(h1 == h2) -assert(math.ceil(1.1) == math.floor(2.9)) +## FIXME: add hash function. +##h1 = math.hash("testing"); h2 = math.hash("test" + "ing") +##assert(h1 == h2) ## Logical statement test val = 0; a = false; b = true diff --git a/tests/lang/core.pk b/tests/lang/core.pk index f2aae8c..1089351 100644 --- a/tests/lang/core.pk +++ b/tests/lang/core.pk @@ -1,8 +1,5 @@ ## 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. @@ -48,61 +45,6 @@ assert(r.as_list == [1, 2, 3, 4]) assert(r.first == 1) assert(r.last == 5) -assert(sin(0) == 0) -assert(sin(PI/2) == 1) - -threshold = 0.0000000000001 - -assert(abs(cos(PI/3) - 0.5) < threshold ) -assert(abs(tan(PI/4) - 1.0) < threshold ) -for i in 0..1000 - assert(abs(sin(i) / cos(i) - tan(i)) < threshold) -end - -assert((cosh(.5) - 1.1276259652063807) < threshold) -assert((tanh(0.5) - 1.127625965206) < threshold) -for i in 0..100 - assert(abs(sinh(i) / cosh(i) - tanh(i)) < threshold) -end - -assert(abs(acos(PI/4) - 0.5) < 0.35) -assert(abs(atan(PI/4) - 0.5) < 0.2) - -assert((acos(0.5) - 1.1276259652063807) < threshold) -assert((atan(0.3) - 1.1276259652063807) < threshold) - -x = -1; interval = 1000 -for i in 0..interval-1 - x += 2/interval - assert(abs(sin(asin(x)) - x) < threshold) - assert(abs(cos(acos(x)) - x) < threshold) - 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) - -## Note that these mathe functions are removed temproarly from the core -## For more information see PR #201 -## -##assert(abs(log2(2) - 1) < threshold) -##assert(abs(log2(1) - 0) < threshold) -##assert(abs(log2(5) - 2.321928094887362) < threshold) -## -##assert(abs(hypot(1,1) - 1.414213562373095) < threshold) -##assert(abs(hypot(3,5) - 5.830951894845301) < threshold) -## -##assert(abs(cbrt(27) - 3) < threshold) -##assert(abs(cbrt(9) - 2.080083823051904) < threshold) -## -##assert(abs(gamma(5) - 24) < threshold) -##assert(abs(gamma(2.2) - 1.101802490879713) < threshold) - - - - # If we got here, that means all test were passed. print('All TESTS PASSED') diff --git a/tests/modules/math.pk b/tests/modules/math.pk new file mode 100644 index 0000000..4356ba8 --- /dev/null +++ b/tests/modules/math.pk @@ -0,0 +1,64 @@ + +from math import * + +assert(ceil(1.1) == floor(2.9)) + +## FIXME: +## temproarly modules cannot define globals via native interface +## and it'll be fixed soon. +PI = 3.14159265358979323846 + +assert(sin(0) == 0) +assert(sin(PI/2) == 1) + +threshold = 0.0000000000001 + +assert(abs(cos(PI/3) - 0.5) < threshold ) +assert(abs(tan(PI/4) - 1.0) < threshold ) +for i in 0..1000 + assert(abs(sin(i) / cos(i) - tan(i)) < threshold) +end + +assert((cosh(.5) - 1.1276259652063807) < threshold) +assert((tanh(0.5) - 1.127625965206) < threshold) +for i in 0..100 + assert(abs(sinh(i) / cosh(i) - tanh(i)) < threshold) +end + +assert(abs(acos(PI/4) - 0.5) < 0.35) +assert(abs(atan(PI/4) - 0.5) < 0.2) + +assert((acos(0.5) - 1.1276259652063807) < threshold) +assert((atan(0.3) - 1.1276259652063807) < threshold) + +x = -1; interval = 1000 +for i in 0..interval-1 + x += 2/interval + assert(abs(sin(asin(x)) - x) < threshold) + assert(abs(cos(acos(x)) - x) < threshold) + 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) + +## Note that these mathe functions are removed temproarly from the core +## For more information see PR #201 +## +##assert(abs(log2(2) - 1) < threshold) +##assert(abs(log2(1) - 0) < threshold) +##assert(abs(log2(5) - 2.321928094887362) < threshold) +## +##assert(abs(hypot(1,1) - 1.414213562373095) < threshold) +##assert(abs(hypot(3,5) - 5.830951894845301) < threshold) +## +##assert(abs(cbrt(27) - 3) < threshold) +##assert(abs(cbrt(9) - 2.080083823051904) < threshold) +## +##assert(abs(gamma(5) - 24) < threshold) +##assert(abs(gamma(2.2) - 1.101802490879713) < threshold) + +# 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 index 140b7e5..1ba5fc9 100644 --- a/tests/native/README.md +++ b/tests/native/README.md @@ -1,7 +1,7 @@ ## Example on how to integrate pocket VM with in your application. -- Including this example this repository contains several examples on how to integrate -pocket VM with your application +- Including this example this repository contains several examples on how to +integrate pocket VM with your application - These examples (currently 2 examples) - The `cli/` application - The `docs/try/main.c` web assembly version of pocketlang diff --git a/tests/native/example1.c b/tests/native/example1.c index c490613..12446fa 100644 --- a/tests/native/example1.c +++ b/tests/native/example1.c @@ -10,17 +10,17 @@ // The pocket script we're using to test. static const char* code = - " from YourModule import variableToC \n" - " a = 42 \n" - " b = variableToC(a) \n" - " print('[pocket] b =', b) \n" + " from my_module import cFunction \n" + " a = 42 \n" + " b = cFunction(a) \n" + " print('[pocket] b = $b') \n" ; /*****************************************************************************/ /* MODULE FUNCTION */ /*****************************************************************************/ -static void variableToC(PKVM* vm) { +static void cFunction(PKVM* vm) { // Get the parameter from pocket VM. double a; @@ -36,15 +36,7 @@ static void variableToC(PKVM* vm) { /* POCKET VM CALLBACKS */ /*****************************************************************************/ -// Error report callback. -static 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. -static void stdoutWrite(PKVM* vm, const char* text) { +static void stdoutCallback(PKVM* vm, const char* text) { fprintf(stdout, "%s", text); } @@ -56,21 +48,20 @@ int main(int argc, char** argv) { // Pocket VM configuration. PkConfiguration config = pkNewConfiguration(); - config.error_fn = reportError; - config.write_fn = stdoutWrite; - //config.read_fn = stdinRead; + config.write_fn = stdoutCallback; // Create a new pocket VM. PKVM* vm = pkNewVM(&config); - // Register your module. - PkHandle* your_module = pkNewModule(vm, "YourModule"); - pkModuleAddFunction(vm, your_module, "variableToC", variableToC, 1); - pkReleaseHandle(vm, your_module); + // Registering a native module. + PkHandle* my_module = pkNewModule(vm, "my_module"); + pkModuleAddFunction(vm, my_module, "cFunction", cFunction, 1); + pkRegisterModule(vm, my_module); + pkReleaseHandle(vm, my_module); // The path and the source code. - PkStringPtr source = { code, NULL, NULL, 0, 0 }; - PkStringPtr path = { "./some/path/", NULL, NULL, 0, 0 }; + PkStringPtr source = { .string = code }; + PkStringPtr path = { .string = "./some/path/" }; // Run the code. PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/); diff --git a/tests/native/example2.c b/tests/native/example2.c index 7a9e2ae..2d0b238 100644 --- a/tests/native/example2.c +++ b/tests/native/example2.c @@ -3,174 +3,85 @@ * Distributed Under The MIT License */ +#error Native interface is being refactored and will be completed soon. + // This is an example on how to write your own custom type (Vector here) and // bind it with with the pocket VM. #include -#include -#include -#include +#include /* For malloc */ +#include /* For printf */ +#include /* For strncmp */ +#include /* For sqrt */ // The script we're using to test the native Vector type. static const char* code = - " 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" + " from vector import Vec2 \n" + " print('Class = $Vec2') \n" + " \n" + " v1 = Vec2(1, 2) \n" + " print('v1 = $v1') \n" + " print() \n" + " \n" + " print('v1.x = ${v1.x}') \n" + " print('v1.y = ${v1.y}') \n" + " print('v1.length = ${v1.length}') \n" + " print() \n" + " \n" + " v1.x = 3; v1.y = 4; \n" + " print('v1.x = ${v1.x}') \n" + " print('v1.y = ${v1.y}') \n" + " print('v1.length = ${v1.length}') \n" + " print() \n" + " \n" + " v2 = Vec2(5, 6) \n" + " v3 = v1.add(v2) \n" + " print('v3 = ${v3}') \n" + " print('v3.x = ${v3.x}') \n" + " print('v3.y = ${v3.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 { - 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, uint32_t id, PkStringPtr attrib) { - - switch ((ObjType)id) { - case OBJ_VECTOR: { - Vector* vector = ((Vector*)instance); - - 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, uint32_t id, PkStringPtr attrib) { - - switch ((ObjType)id) { - case OBJ_VECTOR: { - Vector* vector = ((Vector*)instance); - - if (strcmp(attrib.string, "x") == 0) { - double x; // Get the number x. - if (!pkGetArgNumber(vm, 0, &x)) return false; - vector->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->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, uint32_t id) { - free((void*)instance); // Your cleanups. -} - /*****************************************************************************/ /* 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. +typedef struct { + double x, y; +} Vector; + +// Native instance allocation callback. +void* _newVec() { Vector* vec = (Vector*)malloc(sizeof(Vector)); - vec->x = x, vec->y = y; - pkReturnInstNative(vm, (void*)vec, OBJ_VECTOR); + vec->x = 0; + vec->y = 0; + return vec; } -// 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->x = v1->x + v2->x; - v3->y = v1->y + v2->y; +// Native instance de-allocatoion callback. +void _deleteVec(void* vector) { + free(vector); +} - pkReturnInstNative(vm, (void*)v3, OBJ_VECTOR); +// Vec2 'add' method. +void _vec2Add(PKVM* vm) { + Vector* self = (Vector*)pkGetSelf(vm); + // FIXME: + // Temproarly it's not possible to get vector from the args since the native + // interface is being refactored. Will be implemented soon. } // Register the 'Vector' module and it's functions. void registerVector(PKVM* vm) { - PkHandle* vector = pkNewModule(vm, "Vector"); + PkHandle* vector = pkNewModule(vm, "vector"); - pkModuleAddFunction(vm, vector, "new", _vecNew, 2); - pkModuleAddFunction(vm, vector, "add", _vecAdd, 2); + PkHandle* Vec2 = pkNewClass(vm, "Vec2", NULL /*Base Class*/, + vector, _newVec, _deleteVec); + pkClassAddMethod(vm, Vec2, "add", _vec2Add, 1); + pkReleaseHandle(vm, Vec2); + pkRegisterModule(vm, vector); pkReleaseHandle(vm, vector); } @@ -178,35 +89,20 @@ void registerVector(PKVM* vm) { /* 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) { +void stdoutCallback(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.write_fn = stdoutCallback; PKVM* vm = pkNewVM(&config); registerVector(vm); - PkStringPtr source = { code, NULL, NULL, 0, 0 }; - PkStringPtr path = { "./some/path/", NULL, NULL, 0, 0 }; + PkStringPtr source = { .string = code }; + PkStringPtr path = { .string = "./some/path/" }; PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/); pkFreeVM(vm); diff --git a/tests/random/linked_list.pk b/tests/random/linked_list.pk new file mode 100644 index 0000000..26e3c60 --- /dev/null +++ b/tests/random/linked_list.pk @@ -0,0 +1,67 @@ + +class Node + def _init(val) + self.val = val + self.next = null + end + + def _to_string() + return "(${self.val})" + end +end + +class LinkedList + def _init() + self.head = null + end + + def append(node) + if self.head == null + self.head = node + else + last = self.head + while last.next + last = last.next + end + last.next = node + end + end + + def reverse() + curr = self.head + prev = null; next = null + while curr + next = curr.next + curr.next = prev + prev = curr + curr = next + end + self.head = prev + end + + def _to_string() + ret = "" + next = self.head + while next + ret += next._to_string() + ret += " --> " + next = next.next + end + ret += "null" + return ret + end +end + +ll = LinkedList() +ll.append(Node(4)) +ll.append(Node(6)) +ll.append(Node(3)) +ll.append(Node(9)) + +## FIXME: No override supported at the moment. +print(ll._to_string()) + +ll.reverse() + +print(ll._to_string()) + diff --git a/tests/random/lisp_eval.pk b/tests/random/lisp_eval.pk index fc3b403..3493668 100644 --- a/tests/random/lisp_eval.pk +++ b/tests/random/lisp_eval.pk @@ -65,7 +65,7 @@ def eval(expr, ind) return [r[1] / r[2], r[3]] else if isnum(c) - val = str_ord(c) - str_ord('0') + val = ord(c) - ord('0') assert(0 <= val and val < 10) return [val, ind] @@ -93,7 +93,7 @@ end ## Return true if c in numeric. def isnum(c) - k = str_ord(c) - str_ord('0') + k = ord(c) - ord('0') ## TODO: k in 0..10 return (0 <= k and k < 10) end diff --git a/tests/tests.py b/tests/tests.py index ffeded7..637ed1e 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -26,8 +26,13 @@ TEST_SUITE = { "lang/functions.pk", "lang/import.pk", ), + + "Modules Test" : ( + "modules/math.pk", + ), "Random Scripts" : ( + "random/linked_list.pk", "random/lisp_eval.pk", "random/string_algo.pk", ),