From 5bc9dcad6b29f96905a0d0cbe80df5073a271798 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Mon, 6 Jun 2022 02:32:09 +0530 Subject: [PATCH] method bind implemented - MethodBind type added. - Now methods are first class and can be passed around as arguments store as a variable and return it from a function and they're first class callbales. - Can bind instance to a method bind using .bind() method - Class.methods() method added -> return a list of all methods as method binds - Module.globals() method added -> returns a list of all globals of that module. - Var._class -> attribute added which will return the class of the variable - Class.name, MethodBind.name, Closure.name attribute and Modue._name attribute added - Class._docs, Closure._docs, MethodBind._docs attribute added - MethodBind.instance attribute added --- docs/Reference/Math.md | 29 ------ docs/Reference/Path.md | 23 ----- docs/Reference/io.md | 99 +++++++++++++++++++++ docs/Reference/json.md | 17 ++++ docs/Reference/lang.md | 42 +++++++++ docs/Reference/math.md | 145 ++++++++++++++++++++++++++++++ docs/Reference/os.md | 81 +++++++++++++++++ docs/Reference/path.md | 105 ++++++++++++++++++++++ docs/Reference/term.md | 76 ++++++++++++++++ docs/Reference/time.md | 25 ++++++ docs/Reference/types.md | 95 ++++++++++++++++++++ docs/_sidebar.md | 12 ++- scripts/docs_gen.pk | 93 +++++++++++++++++++ src/core/core.c | 182 +++++++++++++++++++++++++++++++++++--- src/core/value.c | 66 +++++++++++--- src/core/value.h | 14 +++ src/core/vm.c | 10 +++ src/include/pocketlang.h | 1 + src/libs/std_io.c | 8 +- tests/lang/builtin_ty.pk | 26 ++++++ tests/lang/class.pk | 2 + tests/native/dl/main.pk | 2 +- tests/random/lisp_eval.pk | 3 +- 23 files changed, 1068 insertions(+), 88 deletions(-) delete mode 100644 docs/Reference/Math.md delete mode 100644 docs/Reference/Path.md create mode 100644 docs/Reference/io.md create mode 100644 docs/Reference/json.md create mode 100644 docs/Reference/lang.md create mode 100644 docs/Reference/math.md create mode 100644 docs/Reference/os.md create mode 100644 docs/Reference/path.md create mode 100644 docs/Reference/term.md create mode 100644 docs/Reference/time.md create mode 100644 docs/Reference/types.md create mode 100644 scripts/docs_gen.pk diff --git a/docs/Reference/Math.md b/docs/Reference/Math.md deleted file mode 100644 index 39138ce..0000000 --- a/docs/Reference/Math.md +++ /dev/null @@ -1,29 +0,0 @@ - -# math - -TODO: this page is incomplete. - -#### math.floor -```ruby -floor(value:num) -> num -``` - -#### math.ceil -```ruby -ceil(value:num) -> num -``` - -#### math.pow -```ruby -pow(a:num, b:num) -> num -``` - -#### math.sqrt -```ruby -sqrt(value:num) -> num -``` - -#### math.abs -```ruby -abs(value:num) -> num -``` diff --git a/docs/Reference/Path.md b/docs/Reference/Path.md deleted file mode 100644 index da38b89..0000000 --- a/docs/Reference/Path.md +++ /dev/null @@ -1,23 +0,0 @@ -# path - -TODO: this page is incomplete. - -#### path.getcwd -```ruby -getcwd() -> str -``` - -#### path.abspath -```ruby -abspath(path:str) -> str -``` - -#### path.relpath -```ruby -relpath(path:str) -> str -``` - -#### path.join -```ruby -join(...path:str) -> str -``` diff --git a/docs/Reference/io.md b/docs/Reference/io.md new file mode 100644 index 0000000..fc9c7cd --- /dev/null +++ b/docs/Reference/io.md @@ -0,0 +1,99 @@ +# io + +### write + +```ruby +io.write(stream:Var, bytes:String) -> Null +``` + +Warning: the function is subjected to be changed anytime soon. +Write [bytes] string to the stream. stream should be any of io.stdin, io.stdout, io.stderr. + +### flush + +```ruby +io.flush() -> Null +``` + +Warning: the function is subjected to be changed anytime soon. +Flush stdout buffer. + +### getc + +```ruby +io.getc() -> String +``` + +Read a single character from stdin and return it. + +## File +A simple file type. + +### open + +```ruby +io.File.open(path:String, mode:String) -> Null +``` + +Opens a file at the [path] with the [mode]. Path should be either absolute or relative to the current working directory. and [mode] can be'r', 'w', 'a' in combination with 'b' (binary) and/or '+' (extended). +``` + mode | 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 | +``` + +### read + +```ruby +io.File.read(count:Number) -> String +``` + +Reads [count] number of bytes from the file and return it as String.If the count is -1 it'll read till the end of file and return it. + +### write + +```ruby +io.File.write(data:String) -> Null +``` + +Write the [data] to the file. Since pocketlang string support any validbyte value in it's string, binary data can also be written with strings. + +### getline + +```ruby +io.File.getline() -> String +``` + +Reads a line from the file and return it as string. This function can only be used for files that are opened with text mode. + +### close + +```ruby +io.File.close() +``` + +Closes the opend file. + +### seek + +```ruby +io.File.seek(offset:Number, whence:Number) -> Null +``` + +Move the file read/write offset. where [offset] is the offset from [whence] which should be any of the bellow three. + 0: Begining of the file. + 1: Current position. + 2: End of the file. + +### tell + +```ruby +io.File.tell() -> Number +``` + +Returns the read/write position of the file. diff --git a/docs/Reference/json.md b/docs/Reference/json.md new file mode 100644 index 0000000..db36243 --- /dev/null +++ b/docs/Reference/json.md @@ -0,0 +1,17 @@ +# json + +### parse + +```ruby +json.parse(json_str:String) -> Var +``` + +Parse a json string into pocket lang object. + +### print + +```ruby +json.print(value:Var, pretty:Bool=false) +``` + +Render a pocketlang value into text. Takes an optional argument pretty, if true it'll pretty print the output. diff --git a/docs/Reference/lang.md b/docs/Reference/lang.md new file mode 100644 index 0000000..9d1f29a --- /dev/null +++ b/docs/Reference/lang.md @@ -0,0 +1,42 @@ +# lang + +### gc + +```ruby +lang.gc() -> Number +``` + +Trigger garbage collection and return the amount of bytes cleaned. + +### disas + +```ruby +lang.disas(fn:Closure) -> String +``` + +Returns the disassembled opcode of the function [fn]. + +### backtrace + +```ruby +lang.backtrace() -> String +``` + +Returns the backtrace as a string, each line is formated as ';; +'. + +### modules + +```ruby +lang.modules() -> List +``` + +Returns the list of all registered modules. + +### debug_break + +```ruby +lang.debug_break() -> Null +``` + +A debug function for development (will be removed). diff --git a/docs/Reference/math.md b/docs/Reference/math.md new file mode 100644 index 0000000..d0d829c --- /dev/null +++ b/docs/Reference/math.md @@ -0,0 +1,145 @@ +# math + +### floor + +```ruby +math.floor(value:Numberber) -> Numberber +``` + +Return the floor value. + +### ceil + +```ruby +math.ceil(value:Number) -> Number +``` + +Returns the ceiling value. + +### pow + +```ruby +math.pow(a:Number, b:Number) -> Number +``` + +Returns the power 'b' of 'a' similler to a**b. + +### sqrt + +```ruby +math.sqrt(value:Number) -> Number +``` + +Returns the square root of the value + +### abs + +```ruby +math.abs(value:Number) -> Number +``` + +Returns the absolute value. + +### sign + +```ruby +math.sign(value:Number) -> Number +``` + +return the sign of the which is one of (+1, 0, -1). + +### sin + +```ruby +math.sin(rad:Number) -> Number +``` + +Return the sine value of the argument [rad] which is an angle expressed in radians. + +### cos + +```ruby +math.cos(rad:Number) -> Number +``` + +Return the cosine value of the argument [rad] which is an angle expressed in radians. + +### tan + +```ruby +math.tan(rad:Number) -> Number +``` + +Return the tangent value of the argument [rad] which is an angle expressed in radians. + +### sinh + +```ruby +math.sinh(val:Number) -> Number +``` + +Return the hyperbolic sine value of the argument [val]. + +### cosh + +```ruby +math.cosh(val:Number) -> Number +``` + +Return the hyperbolic cosine value of the argument [val]. + +### tanh + +```ruby +math.tanh(val:Number) -> Number +``` + +Return the hyperbolic tangent value of the argument [val]. + +### asin + +```ruby +math.asin(num:Number) -> Number +``` + +Return the arcsine value of the argument [num] which is an angle expressed in radians. + +### acos + +```ruby +math.acos(num:Number) -> Number +``` + +Return the arc cosine value of the argument [num] which is an angle expressed in radians. + +### atan + +```ruby +math.atan(num:Number) -> Number +``` + +Return the arc tangent value of the argument [num] which is an angle expressed in radians. + +### log10 + +```ruby +math.log10(value:Number) -> Number +``` + +Return the logarithm to base 10 of argument [value] + +### round + +```ruby +math.round(value:Number) -> Number +``` + +Round to nearest integer, away from zero and return the number. + +### rand + +```ruby +math.rand() -> Number +``` + +Return a random runber in the range of 0..0x7fff. diff --git a/docs/Reference/os.md b/docs/Reference/os.md new file mode 100644 index 0000000..46ff9aa --- /dev/null +++ b/docs/Reference/os.md @@ -0,0 +1,81 @@ +# os + +### getcwd + +```ruby +os.getcwd() -> String +``` + +Returns the current working directory + +### chdir + +```ruby +os.chdir(path:String) +``` + +Change the current working directory + +### mkdir + +```ruby +os.mkdir(path:String) +``` + +Creates a directory at the path. The path should be valid. + +### rmdir + +```ruby +os.rmdir(path:String) +``` + +Removes an empty directory at the path. + +### unlink + +```ruby +os.rmdir(path:String) +``` + +Removes a file at the path. + +### moditime + +```ruby +os.moditime(path:String) -> Number +``` + +Returns the modified timestamp of the file. + +### filesize + +```ruby +os.filesize(path:String) -> Number +``` + +Returns the file size in bytes. + +### system + +```ruby +os.system(cmd:String) -> Number +``` + +Execute the command in a subprocess, Returns the exit code of the child process. + +### getenv + +```ruby +os.getenv(name:String) -> String +``` + +Returns the environment variable as String if it exists otherwise it'll return null. + +### exepath + +```ruby +os.exepath() -> String +``` + +Returns the path of the pocket interpreter executable. diff --git a/docs/Reference/path.md b/docs/Reference/path.md new file mode 100644 index 0000000..8400182 --- /dev/null +++ b/docs/Reference/path.md @@ -0,0 +1,105 @@ +# path + +### getcwd + +```ruby +path.getcwd() -> String +``` + +Returns the current working directory. + +### abspath + +```ruby +path.abspath(path:String) -> String +``` + +Returns the absolute path of the [path]. + +### relpath + +```ruby +path.relpath(path:String, from:String) -> String +``` + +Returns the relative path of the [path] argument from the [from] directory. + +### join + +```ruby +path.join(...) -> String +``` + +Joins path with path seperator and return it. The maximum count of paths which can be joined for a call is MAX_JOIN_PATHS. + +### normpath + +```ruby +path.normpath(path:String) -> String +``` + +Returns the normalized path of the [path]. + +### basename + +```ruby +path.basename(path:String) -> String +``` + +Returns the final component for the path + +### dirname + +```ruby +path.dirname(path:String) -> String +``` + +Returns the directory of the path. + +### isabspath + +```ruby +path.isabspath(path:String) -> Bool +``` + +Returns true if the path is absolute otherwise false. + +### getext + +```ruby +path.getext(path:String) -> String +``` + +Returns the file extension of the path. + +### exists + +```ruby +path.exists(path:String) -> String +``` + +Returns true if the file exists. + +### isfile + +```ruby +path.isfile(path:String) -> Bool +``` + +Returns true if the path is a file. + +### isdir + +```ruby +path.isdir(path:String) -> Bool +``` + +Returns true if the path is a directory. + +### listdir + +```ruby +path.listdir(path:String='.') -> List +``` + +Returns all the entries in the directory at the [path]. diff --git a/docs/Reference/term.md b/docs/Reference/term.md new file mode 100644 index 0000000..911e280 --- /dev/null +++ b/docs/Reference/term.md @@ -0,0 +1,76 @@ +# term + +### init + +```ruby +term.init(capture_events:Bool) -> Null +``` + +Initialize terminal with raw mode for tui applications, set [capture_events] true to enable event handling. + +### cleanup + +```ruby +term.cleanup() -> Null +``` + +Cleanup and resotre the last terminal state. + +### isatty + +```ruby +term.isatty() -> Bool +``` + +Returns true if both stdin and stdout are tty. + +### new_screen_buffer + +```ruby +term.new_screen_buffer() -> Null +``` + +Switch to an alternative screen buffer. + +### restore_screen_buffer + +```ruby +term.restore_screen_buffer() -> Null +``` + +Restore the alternative buffer which was created with term.new_screen_buffer() + +### getsize + +```ruby +term.getsize() -> types.Vector +``` + +Returns the screen size. + +### getposition + +```ruby +term.getposition() -> types.Vector +``` + +Returns the cursor position in the screen on a zero based coordinate. + +### read_event + +```ruby +term.read_event(event:term.Event) -> Bool +``` + +Read an event and update the argument [event] and return true.If no event was read it'll return false. + +## Event +The terminal event type, that'll be used at term.read_event function to fetch events. + +### binary_mode + +```ruby +term.binary_mode() -> Null +``` + +On windows it'll set stdout to binary mode, on other platforms this function won't make make any difference. diff --git a/docs/Reference/time.md b/docs/Reference/time.md new file mode 100644 index 0000000..e7808ad --- /dev/null +++ b/docs/Reference/time.md @@ -0,0 +1,25 @@ +# time + +### epoch + +```ruby +time() -> Number +``` + +Returns the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). + +### sleep + +```ruby +sleep(t:num) -> Number +``` + +Sleep for [t] milliseconds. + +### clock + +```ruby +clock() -> Number +``` + +Returns the number of clocks passed divied by CLOCKS_PER_SEC. diff --git a/docs/Reference/types.md b/docs/Reference/types.md new file mode 100644 index 0000000..73b85e2 --- /dev/null +++ b/docs/Reference/types.md @@ -0,0 +1,95 @@ +# types + +### hashable + +```ruby +types.hashable(value:Var) -> Bool +``` + +Returns true if the [value] is hashable. + +### hash + +```ruby +types.hash(value:Var) -> Number +``` + +Returns the hash of the [value] + +## ByteBuffer +A simple dynamically allocated byte buffer type. This can be used for constructing larger strings without allocating and adding smaller intermeidate strings. + +### [] + +```ruby +types.ByteBuffer.[](index:Number) +``` + + + +### []= + +```ruby +types.ByteBuffer.[]=(index:Number, value:Number) +``` + + + +### reserve + +```ruby +types.ByteBuffer.reserve(count:Number) -> Null +``` + +Reserve [count] number of bytes internally. This is use full if the final size of the buffer is known beforehand to avoid reduce the number of re-allocations. + +### fill + +```ruby +types.ByteBuffer.fill(value:Number) -> Null +``` + +Fill the buffer with the given byte value. Note that the value must be in between 0 and 0xff inclusive. + +### clear + +```ruby +types.ByteBuffer.clear() -> Null +``` + +Clear the buffer values. + +### write + +```ruby +types.ByteBuffer.write(data:Number|String) -> Null +``` + +Writes the data to the buffer. If the [data] is a number that should be in between 0 and 0xff inclusively. If the [data] is a string all the bytes of the string will be written to the buffer. + +### string + +```ruby +types.ByteBuffer.string() -> String +``` + +Returns the buffered values as String. + +### count + +```ruby +types.ByteBuffer.count() -> Number +``` + +Returns the number of bytes that have written to the buffer. + +## Vector +A simple vector type contains x, y, and z components. + +### _repr + +```ruby +types.Vector._repr() +``` + + diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 85f1aed..bdd43e4 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,8 +1,14 @@ - * Getting Started * [Home](/) * [Language Manual](/GettingStarted/LanguageManual.md) * Library Reference - * [math](/Reference/Math.md) - * [path](/Reference/Path.md) + * [io](/Reference/io.md) + * [json](/Reference/json.md) + * [time](/Reference/time.md) + * [lang](/Reference/lang.md) + * [path](/Reference/path.md) + * [os](/Reference/os.md) + * [term](/Reference/term.md) + * [types](/Reference/types.md) + * [math](/Reference/math.md) diff --git a/scripts/docs_gen.pk b/scripts/docs_gen.pk new file mode 100644 index 0000000..9e68a81 --- /dev/null +++ b/scripts/docs_gen.pk @@ -0,0 +1,93 @@ +#!pocket +## Copyright (c) 2020-2021 Thakee Nathees +## Copyright (c) 2021-2022 Pocketlang Contributors +## Distributed Under The MIT License + +import lang +from path import dirname, join, normpath + +ROOT_PATH = normpath(join(dirname(__file__), '..')) + +REFERENCE_DIR = join(ROOT_PATH, 'docs/Reference/') +SIDEBAR_FILE = join(ROOT_PATH, 'docs/_sidebar.md') + +sidebar = "\ +* Getting Started + * [Home](/) + * [Language Manual](/GettingStarted/LanguageManual.md) + +* Library Reference +" + +def main() + for module in lang.modules() + ## Dummy module is for testing internals and will be + ##removed soon so skip it. + if module._name == 'dummy' + continue + end + f = open(join(REFERENCE_DIR, module._name + '.md'), 'w') + gen_module_docs(f, module) + f.close() + end + + f = open(SIDEBAR_FILE, 'w') + f.write(sidebar) + f.close() + +end + +## Write the [module]'s documentation content to the file [f]. +def gen_module_docs(f, module) + name = module._name + sidebar += ' * [$name](/Reference/$name.md)\n' + f.write('# $name\n') + for global in module.globals() + + { ## Map as switch statement alternative. + Null: fn end, + Number: fn end, + String: fn end, + + Closure : fn + write_fn_doc(f, global) + end, + + Class: fn + write_cls_doc(f, global) + end, + + } [global._class]() + end +end + +## Write the function's documentation to the file [f]. +def write_fn_doc(f, func) + f.write('\n') + f.write('### ${func.name}\n') + f.write('\n') + i = func._docs.find('\n\n') + symbol = func._docs[0..i-1] + desc = func._docs[i+1..-1] + f.write("```ruby\n$symbol\n```\n") + f.write(desc) + f.write('\n') +end + +## Write the class's documentation to the file [f]. +def write_cls_doc(f, cls) + f.write('\n') + f.write('## ${cls.name}\n') + f.write('${cls._docs}\n') + for method in cls.methods() + if method.name == '_init' + continue ## Constructor. + end + write_fn_doc(f, method) + end +end + + +if _name == "@main" + main() +end diff --git a/src/core/core.c b/src/core/core.c index a404d77..c50cb24 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -145,9 +145,11 @@ void initializeModule(PKVM* vm, Module* module, bool is_main) { String *path = module->path, *name = NULL; if (is_main) { - // TODO: consider static string "__main__" stored in PKVM. to reduce + // TODO: consider static string "@main" stored in PKVM. to reduce // allocations everytime here. - name = newString(vm, "__main__"); + ASSERT(module->name == NULL, OOPS); + name = newString(vm, "@main"); + module->name = name; vmPushTempRef(vm, &name->_super); // _main. } else { ASSERT(module->name != NULL, OOPS); @@ -163,7 +165,7 @@ void initializeModule(PKVM* vm, Module* module, bool is_main) { moduleSetGlobal(vm, module, "__file__", 8, VAR_OBJ(path)); } - moduleSetGlobal(vm, module, "__name__", 8, VAR_OBJ(name)); + moduleSetGlobal(vm, module, "_name", 5, VAR_OBJ(name)); if (is_main) vmPopTempRef(vm); // _main. } @@ -272,7 +274,7 @@ static void _collectMethods(PKVM* vm, List* list, Class* cls) { /*****************************************************************************/ DEF(coreHelp, - "help([value:Closure|Class]) -> Null", + "help([value:Closure|MethodBind|Class]) -> Null", "It'll print the docstring the object and return.") { int argc = ARGC; @@ -305,6 +307,18 @@ DEF(coreHelp, vm->config.stdout_write(vm, closure->fn->name); vm->config.stdout_write(vm, "()' doesn't have a docstring.\n"); } + } else if (IS_OBJ_TYPE(value, OBJ_METHOD_BIND)) { + MethodBind* mb = (MethodBind*) AS_OBJ(value); + // If there ins't an io function callback, we're done. + + if (mb->method->fn->docstring != NULL) { + vm->config.stdout_write(vm, mb->method->fn->docstring); + vm->config.stdout_write(vm, "\n\n"); + } else { + vm->config.stdout_write(vm, "method '"); + vm->config.stdout_write(vm, mb->method->fn->name); + vm->config.stdout_write(vm, "()' doesn't have a docstring.\n"); + } } else if (IS_OBJ_TYPE(value, OBJ_CLASS)) { Class* cls = (Class*) AS_OBJ(value); if (cls->docstring != NULL) { @@ -316,7 +330,8 @@ DEF(coreHelp, vm->config.stdout_write(vm, "' doesn't have a docstring.\n"); } } else { - RET_ERR(newString(vm, "Expected a closure or class to get help.")); + RET_ERR(newString(vm, "Expected a Closure, MethodBind or " + "Class to get help.")); } } @@ -405,7 +420,7 @@ DEF(coreAssert, String* msg = NULL; if (argc == 2) { - if (AS_OBJ(ARG(2))->type != OBJ_STRING) { + if (!IS_OBJ_TYPE(ARG(2), OBJ_STRING)) { msg = varToString(vm, ARG(2), false); if (msg == NULL) return; //< Error at _to_string override. @@ -828,7 +843,14 @@ DEF(stdLangModules, vmPushTempRef(vm, &list->_super); // list. for (uint32_t i = 0; i < vm->modules->capacity; i++) { if (!IS_UNDEF(vm->modules->entries[i].key)) { - listAppend(vm, list, vm->modules->entries[i].value); + Var entry = vm->modules->entries[i].value; + ASSERT(IS_OBJ_TYPE(entry, OBJ_MODULE), OOPS); + Module* module = (Module*) AS_OBJ(entry); + ASSERT(module->name != NULL, OOPS); + if (module->name->data[0] == SPECIAL_NAME_CHAR) { + continue; + } + listAppend(vm, list, entry); } } vmPopTempRef(vm); // list. @@ -1268,6 +1290,78 @@ DEF(_mapPop, RET(value); } +DEF(_methodBindBind, + "MethodBind.bind(instance:Var) -> MethodBind", + "Bind the method to the instance and the method bind will be returned. The " + "method should be a valid method of the instance. ie. the instance's " + "interitance tree should contain the method.") { + + MethodBind* self = (MethodBind*) AS_OBJ(SELF); + + // We can only bind the method if the instance has that method. + String* method_name = newString(vm, self->method->fn->name); + vmPushTempRef(vm, &method_name->_super); // method_name. + + Var instance = ARG(1); + + Closure* method; + if (!hasMethod(vm, instance, method_name, &method) + || method != self->method) { + VM_SET_ERROR(vm, newString(vm, "Cannot bind method, instance and method " + "types miss-match.")); + return; + } + + self->instance = instance; + vmPopTempRef(vm); // method_name. + + RET(SELF); +} + +DEF(_classMethods, + "Class.methods() -> List", + "Returns a list of unbound MethodBind of the class.") { + + Class* self = (Class*) AS_OBJ(SELF); + + List* list = newList(vm, self->methods.count); + vmPushTempRef(vm, &list->_super); // list. + for (int i = 0; i < (int) self->methods.count; i++) { + Closure* method = self->methods.data[i]; + ASSERT(method->fn->name, OOPS); + if (method->fn->name[0] == SPECIAL_NAME_CHAR) continue; + MethodBind* mb = newMethodBind(vm, method); + vmPushTempRef(vm, &mb->_super); // mb. + listAppend(vm, list, VAR_OBJ(mb)); + vmPopTempRef(vm); // mb. + } + vmPopTempRef(vm); // list. + + RET(VAR_OBJ(list)); + +} + +DEF(_moduleGlobals, + "Module.globals() -> List", + "Returns a list of all the globals in the module. Since classes and " + "functinos are also globals to a module it'll contain them too.") { + + Module* self = (Module*) AS_OBJ(SELF); + + List* list = newList(vm, self->globals.count); + vmPushTempRef(vm, &list->_super); // list. + for (int i = 0; i < (int) self->globals.count; i++) { + if (moduleGetStringAt(self, + self->global_names.data[i])->data[0] == SPECIAL_NAME_CHAR) { + continue; + } + listAppend(vm, list, self->globals.data[i]); + } + vmPopTempRef(vm); // list. + + RET(VAR_OBJ(list)); +} + DEF(_fiberRun, "Fiber.run(...) -> Var", "Runs the fiber's function with the provided arguments and returns it's " @@ -1383,6 +1477,12 @@ static void initializePrimitiveClasses(PKVM* vm) { ADD_METHOD(PK_MAP, "has", _mapHas, 1); ADD_METHOD(PK_MAP, "pop", _mapPop, 1); + ADD_METHOD(PK_METHOD_BIND, "bind", _methodBindBind, 1); + + ADD_METHOD(PK_CLASS, "methods", _classMethods, 0); + + ADD_METHOD(PK_MODULE, "globals", _moduleGlobals, 0); + ADD_METHOD(PK_FIBER, "run", _fiberRun, -1); ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1); @@ -1867,6 +1967,10 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { VM_SET_ERROR(vm, stringFormat(vm, "'$' object has no attribute named '$'.", \ varTypeName(on), attrib->data)) + if (attrib->hash == CHECK_HASH("_class", 0xa2d93eae)) { + return VAR_OBJ(getClass(vm, on)); + } + if (!IS_OBJ(on)) { ERR_NO_ATTRIB(vm, on, attrib); return VAR_NULL; @@ -1933,6 +2037,9 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { Closure* closure = (Closure*)obj; switch (attrib->hash) { + case CHECK_HASH("name", 0x8d39bde6): + return VAR_OBJ(newString(vm, closure->fn->name)); + case CHECK_HASH("_docs", 0x8fb536a9): if (closure->fn->docstring) { return VAR_OBJ(newString(vm, closure->fn->docstring)); @@ -1943,11 +2050,30 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { case CHECK_HASH("arity", 0x3e96bd7a): return VAR_NUM((double)(closure->fn->arity)); - case CHECK_HASH("name", 0x8d39bde6): - return VAR_OBJ(newString(vm, closure->fn->name)); } } break; + case OBJ_METHOD_BIND: { + MethodBind* mb = (MethodBind*) obj; + + switch (attrib->hash) { + case CHECK_HASH("_docs", 0x8fb536a9): + if (mb->method->fn->docstring) { + return VAR_OBJ(newString(vm, mb->method->fn->docstring)); + } else { + return VAR_OBJ(newString(vm, "")); + } + + case CHECK_HASH("name", 0x8d39bde6): + return VAR_OBJ(newString(vm, mb->method->fn->name)); + + case CHECK_HASH("instance", 0xb86d992): + if (IS_UNDEF(mb->instance)) return VAR_NULL; + return mb->instance; + } + + } break; + case OBJ_UPVALUE: UNREACHABLE(); // Upvalues aren't first class objects. break; @@ -1966,11 +2092,32 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { case OBJ_CLASS: { Class* cls = (Class*) obj; - if (attrib->hash == CHECK_HASH("_docs", 0x8fb536a9)) { - if (cls->docstring) { - return VAR_OBJ(newString(vm, cls->docstring)); - } else { - return VAR_OBJ(newString(vm, "")); + + switch (attrib->hash) { + case CHECK_HASH("_docs", 0x8fb536a9): + if (cls->docstring) { + return VAR_OBJ(newString(vm, cls->docstring)); + } else { + return VAR_OBJ(newString(vm, "")); + } + + case CHECK_HASH("name", 0x8d39bde6): + return VAR_OBJ(newString(vm, cls->name->data)); + + case CHECK_HASH("parent", 0xeacdfcfd): + if (cls->super_class != NULL) { + return VAR_OBJ(cls->super_class); + } else { + return VAR_NULL; + } + } + + for (int i = 0; i < (int)cls->methods.count; i++) { + Closure* method_ = cls->methods.data[i]; + ASSERT(method_->fn->is_method, OOPS); + const char* method_name = method_->fn->name; + if (IS_CSTR_EQ(attrib, method_name, strlen(method_name))) { + return VAR_OBJ(newMethodBind(vm, method_)); } } @@ -1999,6 +2146,13 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { value = mapGet(inst->attribs, VAR_OBJ(attrib)); if (!IS_UNDEF(value)) return value; + Closure* method; + if (hasMethod(vm, on, attrib, &method)) { + MethodBind* mb = newMethodBind(vm, method); + mb->instance = on; + return VAR_OBJ(mb); + } + } break; } diff --git a/src/core/value.c b/src/core/value.c index 3db314f..afa6d7a 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -189,6 +189,15 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { } break; + case OBJ_METHOD_BIND: + { + MethodBind* mb = (MethodBind*) obj; + markObject(vm, &mb->method->_super); + markValue(vm, mb->instance); + + vm->bytes_allocated += sizeof(MethodBind); + } break; + case OBJ_UPVALUE: { Upvalue* upvalue = (Upvalue*)obj; @@ -409,6 +418,16 @@ Closure* newClosure(PKVM* vm, Function* fn) { return closure; } +MethodBind* newMethodBind(PKVM* vm, Closure* method) { + MethodBind* mb = ALLOCATE(vm, MethodBind); + varInitObject(&mb->_super, vm, OBJ_METHOD_BIND); + + mb->method = method; + mb->instance = VAR_UNDEFINED; + + return mb; +} + Upvalue* newUpvalue(PKVM* vm, Var* value) { Upvalue* upvalue = ALLOCATE(vm, Upvalue); varInitObject(&upvalue->_super, vm, OBJ_UPVALUE); @@ -920,6 +939,10 @@ static uint32_t _hashObject(Object* obj) { return utilHashNumber(range->from) ^ utilHashNumber(range->to); } + case OBJ_CLASS: { + return utilHashBits( (int64_t) obj); + } + default: break; } @@ -1168,6 +1191,11 @@ void freeObject(PKVM* vm, Object* self) { return; } + case OBJ_METHOD_BIND: { + DEALLOCATE(vm, self, MethodBind); + return; + } + case OBJ_UPVALUE: { DEALLOCATE(vm, self, Upvalue); return; @@ -1309,6 +1337,7 @@ PkVarType getObjPkVarType(ObjectType type) { case OBJ_MODULE: return PK_MODULE; case OBJ_FUNC: UNREACHABLE(); case OBJ_CLOSURE: return PK_CLOSURE; + case OBJ_METHOD_BIND: return PK_METHOD_BIND; case OBJ_UPVALUE: UNREACHABLE(); case OBJ_FIBER: return PK_FIBER; case OBJ_CLASS: return PK_CLASS; @@ -1327,15 +1356,16 @@ ObjectType getPkVarObjType(PkVarType type) { case PK_NUMBER: UNREACHABLE(); - case PK_STRING: return OBJ_STRING; - case PK_LIST: return OBJ_LIST; - case PK_MAP: return OBJ_MAP; - case PK_RANGE: return OBJ_RANGE; - case PK_MODULE: return OBJ_MODULE; - case PK_CLOSURE: return OBJ_CLOSURE; - case PK_FIBER: return OBJ_FIBER; - case PK_CLASS: return OBJ_CLASS; - case PK_INSTANCE: return OBJ_INST; + case PK_STRING: return OBJ_STRING; + case PK_LIST: return OBJ_LIST; + case PK_MAP: return OBJ_MAP; + case PK_RANGE: return OBJ_RANGE; + case PK_MODULE: return OBJ_MODULE; + case PK_CLOSURE: return OBJ_CLOSURE; + case PK_METHOD_BIND: return OBJ_METHOD_BIND; + case PK_FIBER: return OBJ_FIBER; + case PK_CLASS: return OBJ_CLASS; + case PK_INSTANCE: return OBJ_INST; } UNREACHABLE(); @@ -1365,6 +1395,7 @@ const char* getObjectTypeName(ObjectType type) { case OBJ_MODULE: return "Module"; case OBJ_FUNC: return "Func"; case OBJ_CLOSURE: return "Closure"; + case OBJ_METHOD_BIND: return "MethodBind"; case OBJ_UPVALUE: return "Upvalue"; case OBJ_FIBER: return "Fiber"; case OBJ_CLASS: return "Class"; @@ -1472,7 +1503,7 @@ bool isValuesEqual(Var v1, Var v2) { bool isObjectHashable(ObjectType type) { // Only String and Range are hashable (since they're immutable). - return type == OBJ_STRING || type == OBJ_RANGE; + return type == OBJ_STRING || type == OBJ_RANGE || type == OBJ_CLASS; } // This will prevent recursive list/map from crash when calling to_string, by @@ -1681,7 +1712,7 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff, } case OBJ_FUNC: { - const Function* fn = (const Function*)obj; + const Function* fn = (const Function*) obj; pkByteBufferAddString(buff, vm, "[Func:", 6); pkByteBufferAddString(buff, vm, fn->name, (uint32_t)strlen(fn->name)); pkByteBufferWrite(buff, vm, ']'); @@ -1689,16 +1720,24 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff, } case OBJ_CLOSURE: { - const Closure* closure = (const Closure*)obj; + const Closure* closure = (const Closure*) obj; pkByteBufferAddString(buff, vm, "[Closure:", 9); pkByteBufferAddString(buff, vm, closure->fn->name, (uint32_t)strlen(closure->fn->name)); pkByteBufferWrite(buff, vm, ']'); return; } + case OBJ_METHOD_BIND: { + const MethodBind* mb = (const MethodBind*) obj; + pkByteBufferAddString(buff, vm, "[MethodBind:", 12); + pkByteBufferAddString(buff, vm, mb->method->fn->name, + (uint32_t)strlen(mb->method->fn->name)); + pkByteBufferWrite(buff, vm, ']'); + return; + } case OBJ_FIBER: { - const Fiber* fb = (const Fiber*)obj; + const Fiber* fb = (const Fiber*) obj; pkByteBufferAddString(buff, vm, "[Fiber:", 7); pkByteBufferAddString(buff, vm, fb->closure->fn->name, (uint32_t)strlen(fb->closure->fn->name)); @@ -1784,6 +1823,7 @@ bool toBool(Var v) { case OBJ_MODULE: case OBJ_FUNC: case OBJ_CLOSURE: + case OBJ_METHOD_BIND: case OBJ_UPVALUE: case OBJ_FIBER: case OBJ_CLASS: diff --git a/src/core/value.h b/src/core/value.h index b0098db..ffb12c9 100644 --- a/src/core/value.h +++ b/src/core/value.h @@ -192,6 +192,7 @@ typedef struct Range Range; typedef struct Module Module; typedef struct Function Function; typedef struct Closure Closure; +typedef struct MethodBind MethodBind; typedef struct Upvalue Upvalue; typedef struct Fiber Fiber; typedef struct Class Class; @@ -223,6 +224,7 @@ typedef enum { OBJ_MODULE, OBJ_FUNC, OBJ_CLOSURE, + OBJ_METHOD_BIND, OBJ_UPVALUE, OBJ_FIBER, OBJ_CLASS, @@ -402,7 +404,17 @@ struct Closure { Function* fn; Upvalue* upvalues[DYNAMIC_TAIL_ARRAY]; +}; +// Method bounds are first class callable of methods. That are bound to an +// instace which will be used as the self when the underlying method invoked. +// If the vallue [instance] is VAR_UNDEFINED it's unbound and cannot be +// called. +struct MethodBind { + Object _super; + + Closure* method; + Var instance; }; // In addition to locals (which lives on the stack), a closure has upvalues. @@ -594,6 +606,8 @@ Module* newModule(PKVM* vm); Closure* newClosure(PKVM* vm, Function* fn); +MethodBind* newMethodBind(PKVM* vm, Closure* method); + Upvalue* newUpvalue(PKVM* vm, Var* value); Fiber* newFiber(PKVM* vm, Closure* closure); diff --git a/src/core/vm.c b/src/core/vm.c index 62a3f59..f0b3be1 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -1223,6 +1223,15 @@ L_do_call: if (IS_OBJ_TYPE(callable, OBJ_CLOSURE)) { closure = (const Closure*)AS_OBJ(callable); + } else if (IS_OBJ_TYPE(callable, OBJ_METHOD_BIND)) { + const MethodBind* mb = (const MethodBind*) AS_OBJ(callable); + if (IS_UNDEF(mb->instance)) { + RUNTIME_ERROR(newString(vm, "Cannot call an unbound method.")); + CHECK_ERROR(); + } + fiber->self = mb->instance; + closure = mb->method; + } else if (IS_OBJ_TYPE(callable, OBJ_CLASS)) { Class* cls = (Class*)AS_OBJ(callable); @@ -1422,6 +1431,7 @@ L_do_call: case OBJ_MODULE: case OBJ_FUNC: case OBJ_CLOSURE: + case OBJ_METHOD_BIND: case OBJ_UPVALUE: case OBJ_FIBER: case OBJ_CLASS: diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 1c89d3a..a365306 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -180,6 +180,7 @@ enum PkVarType { PK_RANGE, PK_MODULE, PK_CLOSURE, + PK_METHOD_BIND, PK_FIBER, PK_CLASS, PK_INSTANCE, diff --git a/src/libs/std_io.c b/src/libs/std_io.c index c60491c..9502c55 100644 --- a/src/libs/std_io.c +++ b/src/libs/std_io.c @@ -134,7 +134,7 @@ DEF(_fileOpen, "Opens a file at the [path] with the [mode]. Path should be either " "absolute or relative to the current working directory. and [mode] can be" "'r', 'w', 'a' in combination with 'b' (binary) and/or '+' (extended).\n" - "\n" + "```\n" " mode | If already exists | If does not exist |\n" " -----+-------------------+-------------------|\n" " 'r' | read from start | failure to open |\n" @@ -143,7 +143,7 @@ DEF(_fileOpen, " 'r+' | read from start | error |\n" " 'w+' | destroy contents | create new |\n" " 'a+' | write to end | create new |\n" - "") { + "```") { int argc = pkGetArgc(vm); if (!pkCheckArgcRange(vm, argc, 1, 2)) return; @@ -441,7 +441,7 @@ DEF(_open, "Opens a file at the [path] with the [mode]. Path should be either " "absolute or relative to the current working directory. and [mode] can be" "'r', 'w', 'a' in combination with 'b' (binary) and/or '+' (extended).\n" - "\n" + "```\n" " mode | If already exists | If does not exist |\n" " -----+-------------------+-------------------|\n" " 'r' | read from start | failure to open |\n" @@ -450,7 +450,7 @@ DEF(_open, " 'r+' | read from start | error |\n" " 'w+' | destroy contents | create new |\n" " 'a+' | write to end | create new |\n" - "") { + "```") { pkReserveSlots(vm, 3); // slots[1] = path diff --git a/tests/lang/builtin_ty.pk b/tests/lang/builtin_ty.pk index d80f159..25f9191 100644 --- a/tests/lang/builtin_ty.pk +++ b/tests/lang/builtin_ty.pk @@ -182,5 +182,31 @@ def foo(p1, p2, p3) end assert(foo.name == "foo") assert(foo.arity == 3) +############################################################################### +## METHOD BIND +############################################################################### + +class Foo + def bar() + return self + end +end + +foo = Foo() +bar = foo.bar +assert(bar() == foo) + +class Bar + def baz() + "baz doc string" + return self + end +end + +bar = Bar() +bz = Bar.baz +assert(bz._docs == "baz doc string") +bz.bind(bar)() + ## If we got here, that means all test were passed. print('All TESTS PASSED') diff --git a/tests/lang/class.pk b/tests/lang/class.pk index effba31..a394712 100644 --- a/tests/lang/class.pk +++ b/tests/lang/class.pk @@ -21,6 +21,8 @@ print(a) b = B() assert((b is B) and (b is A)) +assert(B.parent == A) + class Shape def display() return "${self.name} shape" diff --git a/tests/native/dl/main.pk b/tests/native/dl/main.pk index 319b0ea..c11bea3 100644 --- a/tests/native/dl/main.pk +++ b/tests/native/dl/main.pk @@ -3,7 +3,7 @@ ## from either mylib.so, or mylib.dll import mylib -if __name__ == "__main__" +if _name == "@main" ## Call the registered function. print('mylib.hello() = ${mylib.hello()}') end diff --git a/tests/random/lisp_eval.pk b/tests/random/lisp_eval.pk index 3d1023a..076e9eb 100644 --- a/tests/random/lisp_eval.pk +++ b/tests/random/lisp_eval.pk @@ -100,6 +100,7 @@ end ## ------------------------------------------------- -## if __name__ == '__main__' ;) +if _name == '@main' main() +end