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
This commit is contained in:
Thakee Nathees 2022-06-06 02:32:09 +05:30
parent 8e6f347009
commit 5bc9dcad6b
23 changed files with 1068 additions and 88 deletions

View File

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

View File

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

99
docs/Reference/io.md Normal file
View File

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

17
docs/Reference/json.md Normal file
View File

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

42
docs/Reference/lang.md Normal file
View File

@ -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 '<function>;<file>;<line>
'.
### 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).

145
docs/Reference/math.md Normal file
View File

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

81
docs/Reference/os.md Normal file
View File

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

105
docs/Reference/path.md Normal file
View File

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

76
docs/Reference/term.md Normal file
View File

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

25
docs/Reference/time.md Normal file
View File

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

95
docs/Reference/types.md Normal file
View File

@ -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()
```

View File

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

93
scripts/docs_gen.pk Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -180,6 +180,7 @@ enum PkVarType {
PK_RANGE,
PK_MODULE,
PK_CLOSURE,
PK_METHOD_BIND,
PK_FIBER,
PK_CLASS,
PK_INSTANCE,

View File

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

View File

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

View File

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

View File

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

View File

@ -100,6 +100,7 @@ end
## -------------------------------------------------
## if __name__ == '__main__' ;)
if _name == '@main'
main()
end