mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-05 20:26:53 +08:00
Merge pull request #215 from ThakeeNathees/operator-overload
operator overloading.
This commit is contained in:
commit
962f868907
@ -13,6 +13,7 @@
|
||||
/* STD MODULE SOURCES */
|
||||
/*****************************************************************************/
|
||||
|
||||
#include "modules/std_dummy.c"
|
||||
#include "modules/std_io.c"
|
||||
#include "modules/std_path.c"
|
||||
#include "modules/std_math.c"
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void registerModuleDummy(PKVM* vm);
|
||||
void registerModuleIO(PKVM* vm);
|
||||
void registerModulePath(PKVM* vm);
|
||||
void registerModuleMath(PKVM* vm);
|
||||
@ -21,6 +22,7 @@ void registerModuleMath(PKVM* vm);
|
||||
// Registers all the cli modules.
|
||||
#define REGISTER_ALL_MODULES(vm) \
|
||||
do { \
|
||||
registerModuleDummy(vm); \
|
||||
registerModuleIO(vm); \
|
||||
registerModulePath(vm); \
|
||||
registerModuleMath(vm); \
|
||||
|
97
cli/modules/std_dummy.c
Normal file
97
cli/modules/std_dummy.c
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2022 Thakee Nathees
|
||||
* Copyright (c) 2021-2022 Pocketlang Contributors
|
||||
* Distributed Under The MIT License
|
||||
*/
|
||||
|
||||
#include "modules.h"
|
||||
|
||||
// A DUMMY MODULE TO TEST NATIVE INTERFACE AND CLASSES.
|
||||
|
||||
typedef struct {
|
||||
double val;
|
||||
} Dummy;
|
||||
|
||||
void* _newDummy() {
|
||||
Dummy* dummy = NEW_OBJ(Dummy);
|
||||
ASSERT(dummy != NULL, "malloc failed.");
|
||||
dummy->val = 0;
|
||||
return dummy;
|
||||
}
|
||||
|
||||
void _deleteDummy(void* ptr) {
|
||||
Dummy* dummy = (Dummy*)ptr;
|
||||
FREE_OBJ(dummy);
|
||||
}
|
||||
|
||||
DEF(_dummyGetter, "") {
|
||||
const char* name = pkGetSlotString(vm, 1, NULL);
|
||||
Dummy* self = (Dummy*)pkGetSelf(vm);
|
||||
if (strcmp("val", name) == 0) {
|
||||
pkSetSlotNumber(vm, 0, self->val);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DEF(_dummySetter, "") {
|
||||
const char* name = pkGetSlotString(vm, 1, NULL);
|
||||
Dummy* self = (Dummy*)pkGetSelf(vm);
|
||||
if (strcmp("val", name) == 0) {
|
||||
double val;
|
||||
if (!pkValidateSlotNumber(vm, 2, &val)) return;
|
||||
self->val = val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DEF(_dummyEq, "") {
|
||||
|
||||
// TODO: Currently there is no way of getting another native instance
|
||||
// So, it's impossible to check self == other. So for now checking with
|
||||
// number.
|
||||
double value;
|
||||
if (!pkValidateSlotNumber(vm, 1, &value)) return;
|
||||
|
||||
Dummy* self = (Dummy*)pkGetSelf(vm);
|
||||
pkSetSlotBool(vm, 0, value == self->val);
|
||||
}
|
||||
|
||||
DEF(_dummyGt, "") {
|
||||
|
||||
// TODO: Currently there is no way of getting another native instance
|
||||
// So, it's impossible to check self == other. So for now checking with
|
||||
// number.
|
||||
double value;
|
||||
if (!pkValidateSlotNumber(vm, 1, &value)) return;
|
||||
|
||||
Dummy* self = (Dummy*)pkGetSelf(vm);
|
||||
pkSetSlotBool(vm, 0, self->val > value);
|
||||
}
|
||||
|
||||
DEF(_dummyMethod,
|
||||
"Dummy.a_method(n1:num, n2:num) -> num\n"
|
||||
"A dummy method to check dummy method calls. Will take 2 number arguments "
|
||||
"and return the multiplication.") {
|
||||
|
||||
double n1, n2;
|
||||
if (!pkValidateSlotNumber(vm, 1, &n1)) return;
|
||||
if (!pkValidateSlotNumber(vm, 2, &n2)) return;
|
||||
pkSetSlotNumber(vm, 0, n1 * n2);
|
||||
|
||||
}
|
||||
|
||||
void registerModuleDummy(PKVM* vm) {
|
||||
PkHandle* dummy = pkNewModule(vm, "dummy");
|
||||
|
||||
PkHandle* cls_dummy = pkNewClass(vm, "Dummy", NULL, dummy,
|
||||
_newDummy, _deleteDummy);
|
||||
pkClassAddMethod(vm, cls_dummy, "@getter", _dummyGetter, 1);
|
||||
pkClassAddMethod(vm, cls_dummy, "@setter", _dummySetter, 2);
|
||||
pkClassAddMethod(vm, cls_dummy, "==", _dummyEq, 1);
|
||||
pkClassAddMethod(vm, cls_dummy, ">", _dummyGt, 1);
|
||||
pkClassAddMethod(vm, cls_dummy, "a_method", _dummyMethod, 2);
|
||||
pkReleaseHandle(vm, cls_dummy);
|
||||
|
||||
pkRegisterModule(vm, dummy);
|
||||
pkReleaseHandle(vm, dummy);
|
||||
}
|
@ -1,12 +1,6 @@
|
||||
|
||||
// Language features.
|
||||
- Closure (literal function are would become closure).
|
||||
- Utf8 support.
|
||||
- Method
|
||||
Reference: CPython
|
||||
See: https://github.com/python/cpython/blob/6db2db91b96aaa1270c200ec931a2250fe2799c7/Python/ceval.c#L4344-L4380
|
||||
Note: consider dynamic attribute for instances like python and make the variables defined at the class level
|
||||
are static.
|
||||
- Make assert a keyword and disable it on release build.
|
||||
|
||||
// To implement.
|
||||
|
@ -1559,7 +1559,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence
|
||||
/* TK_PIPE */ { NULL, exprBinaryOp, PREC_BITWISE_OR },
|
||||
/* TK_CARET */ { NULL, exprBinaryOp, PREC_BITWISE_XOR },
|
||||
/* TK_ARROW */ NO_RULE,
|
||||
/* TK_PLUS */ { NULL, exprBinaryOp, PREC_TERM },
|
||||
/* TK_PLUS */ { exprUnaryOp, exprBinaryOp, PREC_TERM },
|
||||
/* TK_MINUS */ { exprUnaryOp, exprBinaryOp, PREC_TERM },
|
||||
/* TK_STAR */ { NULL, exprBinaryOp, PREC_FACTOR },
|
||||
/* TK_FSLASH */ { NULL, exprBinaryOp, PREC_FACTOR },
|
||||
@ -1920,26 +1920,32 @@ static void exprBinaryOp(Compiler* compiler) {
|
||||
skipNewLines(compiler);
|
||||
parsePrecedence(compiler, (Precedence)(getRule(op)->precedence + 1));
|
||||
|
||||
// Emits the opcode and 0 (means false) as inplace operation.
|
||||
#define EMIT_BINARY_OP_INPLACE(opcode)\
|
||||
do { emitOpcode(compiler, opcode); emitByte(compiler, 0); } while (false)
|
||||
|
||||
switch (op) {
|
||||
case TK_DOTDOT: emitOpcode(compiler, OP_RANGE); break;
|
||||
case TK_PERCENT: emitOpcode(compiler, OP_MOD); break;
|
||||
case TK_AMP: emitOpcode(compiler, OP_BIT_AND); break;
|
||||
case TK_PIPE: emitOpcode(compiler, OP_BIT_OR); break;
|
||||
case TK_CARET: emitOpcode(compiler, OP_BIT_XOR); break;
|
||||
case TK_PLUS: emitOpcode(compiler, OP_ADD); break;
|
||||
case TK_MINUS: emitOpcode(compiler, OP_SUBTRACT); break;
|
||||
case TK_STAR: emitOpcode(compiler, OP_MULTIPLY); break;
|
||||
case TK_FSLASH: emitOpcode(compiler, OP_DIVIDE); break;
|
||||
case TK_GT: emitOpcode(compiler, OP_GT); break;
|
||||
case TK_LT: emitOpcode(compiler, OP_LT); break;
|
||||
case TK_EQEQ: emitOpcode(compiler, OP_EQEQ); break;
|
||||
case TK_NOTEQ: emitOpcode(compiler, OP_NOTEQ); break;
|
||||
case TK_GTEQ: emitOpcode(compiler, OP_GTEQ); break;
|
||||
case TK_LTEQ: emitOpcode(compiler, OP_LTEQ); break;
|
||||
case TK_SRIGHT: emitOpcode(compiler, OP_BIT_RSHIFT); break;
|
||||
case TK_SLEFT: emitOpcode(compiler, OP_BIT_LSHIFT); break;
|
||||
case TK_IN: emitOpcode(compiler, OP_IN); break;
|
||||
case TK_IS: emitOpcode(compiler, OP_IS); break;
|
||||
case TK_DOTDOT: emitOpcode(compiler, OP_RANGE); break;
|
||||
case TK_PERCENT: EMIT_BINARY_OP_INPLACE(OP_MOD); break;
|
||||
case TK_PLUS: EMIT_BINARY_OP_INPLACE(OP_ADD); break;
|
||||
case TK_MINUS: EMIT_BINARY_OP_INPLACE(OP_SUBTRACT); break;
|
||||
case TK_STAR: EMIT_BINARY_OP_INPLACE(OP_MULTIPLY); break;
|
||||
case TK_FSLASH: EMIT_BINARY_OP_INPLACE(OP_DIVIDE); break;
|
||||
case TK_AMP: EMIT_BINARY_OP_INPLACE(OP_BIT_AND); break;
|
||||
case TK_PIPE: EMIT_BINARY_OP_INPLACE(OP_BIT_OR); break;
|
||||
case TK_CARET: EMIT_BINARY_OP_INPLACE(OP_BIT_XOR); break;
|
||||
case TK_SRIGHT: EMIT_BINARY_OP_INPLACE(OP_BIT_RSHIFT); break;
|
||||
case TK_SLEFT: EMIT_BINARY_OP_INPLACE(OP_BIT_LSHIFT); break;
|
||||
#undef EMIT_BINARY_OP_INPLACE
|
||||
|
||||
case TK_GT: emitOpcode(compiler, OP_GT); break;
|
||||
case TK_LT: emitOpcode(compiler, OP_LT); break;
|
||||
case TK_EQEQ: emitOpcode(compiler, OP_EQEQ); break;
|
||||
case TK_NOTEQ: emitOpcode(compiler, OP_NOTEQ); break;
|
||||
case TK_GTEQ: emitOpcode(compiler, OP_GTEQ); break;
|
||||
case TK_LTEQ: emitOpcode(compiler, OP_LTEQ); break;
|
||||
case TK_IN: emitOpcode(compiler, OP_IN); break;
|
||||
case TK_IS: emitOpcode(compiler, OP_IS); break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@ -1952,6 +1958,7 @@ static void exprUnaryOp(Compiler* compiler) {
|
||||
|
||||
switch (op) {
|
||||
case TK_TILD: emitOpcode(compiler, OP_BIT_NOT); break;
|
||||
case TK_PLUS: emitOpcode(compiler, OP_POSITIVE); break;
|
||||
case TK_MINUS: emitOpcode(compiler, OP_NEGATIVE); break;
|
||||
case TK_NOT: emitOpcode(compiler, OP_NOT); break;
|
||||
default:
|
||||
@ -2361,20 +2368,25 @@ static void emitLoopJump(Compiler* compiler) {
|
||||
}
|
||||
|
||||
static void emitAssignedOp(Compiler* compiler, TokenType assignment) {
|
||||
// Emits the opcode and 1 (means true) as inplace operation.
|
||||
#define EMIT_BINARY_OP_INPLACE(opcode)\
|
||||
do { emitOpcode(compiler, opcode); emitByte(compiler, 1); } while (false)
|
||||
|
||||
switch (assignment) {
|
||||
case TK_PLUSEQ: emitOpcode(compiler, OP_ADD); break;
|
||||
case TK_MINUSEQ: emitOpcode(compiler, OP_SUBTRACT); break;
|
||||
case TK_STAREQ: emitOpcode(compiler, OP_MULTIPLY); break;
|
||||
case TK_DIVEQ: emitOpcode(compiler, OP_DIVIDE); break;
|
||||
case TK_MODEQ: emitOpcode(compiler, OP_MOD); break;
|
||||
case TK_ANDEQ: emitOpcode(compiler, OP_BIT_AND); break;
|
||||
case TK_OREQ: emitOpcode(compiler, OP_BIT_OR); break;
|
||||
case TK_XOREQ: emitOpcode(compiler, OP_BIT_XOR); break;
|
||||
case TK_SRIGHTEQ: emitOpcode(compiler, OP_BIT_RSHIFT); break;
|
||||
case TK_SLEFTEQ: emitOpcode(compiler, OP_BIT_LSHIFT); break;
|
||||
case TK_PLUSEQ: EMIT_BINARY_OP_INPLACE(OP_ADD); break;
|
||||
case TK_MINUSEQ: EMIT_BINARY_OP_INPLACE(OP_SUBTRACT); break;
|
||||
case TK_STAREQ: EMIT_BINARY_OP_INPLACE(OP_MULTIPLY); break;
|
||||
case TK_DIVEQ: EMIT_BINARY_OP_INPLACE(OP_DIVIDE); break;
|
||||
case TK_MODEQ: EMIT_BINARY_OP_INPLACE(OP_MOD); break;
|
||||
case TK_ANDEQ: EMIT_BINARY_OP_INPLACE(OP_BIT_AND); break;
|
||||
case TK_OREQ: EMIT_BINARY_OP_INPLACE(OP_BIT_OR); break;
|
||||
case TK_XOREQ: EMIT_BINARY_OP_INPLACE(OP_BIT_XOR); break;
|
||||
case TK_SRIGHTEQ: EMIT_BINARY_OP_INPLACE(OP_BIT_RSHIFT); break;
|
||||
case TK_SLEFTEQ: EMIT_BINARY_OP_INPLACE(OP_BIT_LSHIFT); break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
#undef EMIT_BINARY_OP_INPLACE
|
||||
}
|
||||
}
|
||||
|
||||
@ -2497,6 +2509,67 @@ static int compileClass(Compiler* compiler) {
|
||||
return cls_index;
|
||||
}
|
||||
|
||||
// Match operator mathod definition. This will match the operator overloading
|
||||
// method syntax of ruby.
|
||||
static bool matchOperatorMethod(Compiler* compiler,
|
||||
const char** name, int* length, int* argc) {
|
||||
ASSERT((name != NULL) && (length != NULL) && (argc != NULL), OOPS);
|
||||
#define _RET(_name, _argc) \
|
||||
do { \
|
||||
*name = _name; *length = (int)strlen(_name); \
|
||||
*argc = _argc; \
|
||||
return true; \
|
||||
} while (false)
|
||||
|
||||
if (match(compiler, TK_PLUS)) {
|
||||
if (match(compiler, TK_SELF)) _RET("+self", 0);
|
||||
else _RET("+", 1);
|
||||
}
|
||||
if (match(compiler, TK_MINUS)) {
|
||||
if (match(compiler, TK_SELF)) _RET("-self", 0);
|
||||
else _RET("-", 1);
|
||||
}
|
||||
if (match(compiler, TK_TILD)){
|
||||
if (match(compiler, TK_SELF)) _RET("~self", 0);
|
||||
syntaxError(compiler, compiler->parser.previous,
|
||||
"Expected keyword self for unary operator definition.");
|
||||
return false;
|
||||
}
|
||||
if (match(compiler, TK_NOT)) {
|
||||
if (match(compiler, TK_SELF)) _RET("!self", 0);
|
||||
syntaxError(compiler, compiler->parser.previous,
|
||||
"Expected keyword self for unary operator definition.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (match(compiler, TK_PLUSEQ)) _RET("+=", 1);
|
||||
if (match(compiler, TK_MINUSEQ)) _RET("-=", 1);
|
||||
if (match(compiler, TK_STAR)) _RET("*", 1);
|
||||
if (match(compiler, TK_STAREQ)) _RET("*=", 1);
|
||||
if (match(compiler, TK_FSLASH)) _RET("/", 1);
|
||||
if (match(compiler, TK_DIVEQ)) _RET("/=", 1);
|
||||
if (match(compiler, TK_PERCENT)) _RET("%", 1);
|
||||
if (match(compiler, TK_MODEQ)) _RET("%=", 1);
|
||||
if (match(compiler, TK_AMP)) _RET("&", 1);
|
||||
if (match(compiler, TK_ANDEQ)) _RET("&=", 1);
|
||||
if (match(compiler, TK_PIPE)) _RET("|", 1);
|
||||
if (match(compiler, TK_OREQ)) _RET("|=", 1);
|
||||
if (match(compiler, TK_CARET)) _RET("^", 1);
|
||||
if (match(compiler, TK_XOREQ)) _RET("^=", 1);
|
||||
if (match(compiler, TK_SLEFT)) _RET("<<", 1);
|
||||
if (match(compiler, TK_SLEFTEQ)) _RET("<<=", 1);
|
||||
if (match(compiler, TK_SRIGHT)) _RET(">>", 1);
|
||||
if (match(compiler, TK_SRIGHTEQ)) _RET(">>=", 1);
|
||||
if (match(compiler, TK_EQEQ)) _RET("==", 1);
|
||||
if (match(compiler, TK_GT)) _RET(">", 1);
|
||||
if (match(compiler, TK_LT)) _RET("<", 1);
|
||||
if (match(compiler, TK_DOTDOT)) _RET("..", 1);
|
||||
if (match(compiler, TK_IN)) _RET("in", 1);
|
||||
|
||||
return false;
|
||||
#undef _RET
|
||||
}
|
||||
|
||||
// Compile a function, if it's a literal function after this call a closure of
|
||||
// the function will be at the stack top, toplevel functions will be assigned
|
||||
// to a global variable and popped, and methods will be bind to the class and
|
||||
@ -2506,16 +2579,32 @@ static void compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
const char* name;
|
||||
int name_length;
|
||||
|
||||
// If it's an operator method the bellow value will set to a positive value
|
||||
// (the argc of the method) it requires to throw a compile time error.
|
||||
int operator_argc = -2;
|
||||
|
||||
if (fn_type != FUNC_LITERAL) {
|
||||
consume(compiler, TK_NAME, "Expected a function name.");
|
||||
name = compiler->parser.previous.start;
|
||||
name_length = compiler->parser.previous.length;
|
||||
|
||||
if (match(compiler, TK_NAME)) {
|
||||
name = compiler->parser.previous.start;
|
||||
name_length = compiler->parser.previous.length;
|
||||
|
||||
} else if (fn_type == FUNC_METHOD &&
|
||||
matchOperatorMethod(compiler, &name, &name_length, &operator_argc)) {
|
||||
|
||||
// Check if any error has been set by operator definition.
|
||||
} else if (!compiler->parser.has_syntax_error) {
|
||||
syntaxError(compiler, compiler->parser.previous,
|
||||
"Expected a function name.");
|
||||
}
|
||||
|
||||
} else {
|
||||
name = LITERAL_FN_NAME;
|
||||
name_length = (int)strlen(name);
|
||||
}
|
||||
|
||||
if (compiler->parser.has_syntax_error) return;
|
||||
|
||||
int fn_index;
|
||||
Function* func = newFunction(compiler->parser.vm, name, name_length,
|
||||
compiler->module, false, NULL, &fn_index);
|
||||
@ -2532,7 +2621,7 @@ static void compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
global_index = compilerAddVariable(compiler, name, name_length, name_line);
|
||||
}
|
||||
|
||||
if (fn_type == FUNC_METHOD && strncmp(name, "_init", name_length) == 0) {
|
||||
if (fn_type == FUNC_METHOD && strncmp(name, CTOR_NAME, name_length) == 0) {
|
||||
fn_type = FUNC_CONSTRUCTOR;
|
||||
}
|
||||
|
||||
@ -2576,6 +2665,11 @@ static void compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
consume(compiler, TK_RPARAN, "Expected ')' after parameter list.");
|
||||
}
|
||||
|
||||
if (operator_argc >= 0 && argc != operator_argc) {
|
||||
semanticError(compiler, compiler->parser.previous,
|
||||
"Expected exactly %d parameters.", operator_argc);
|
||||
}
|
||||
|
||||
func->arity = argc;
|
||||
compilerChangeStack(compiler, argc);
|
||||
|
||||
|
528
src/pk_core.c
528
src/pk_core.c
@ -153,6 +153,81 @@ void initializeCore(PKVM* vm) {
|
||||
initializePrimitiveClasses(vm);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* INTERNAL FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Returns the string value of the variable, a wrapper of toString() function
|
||||
// but for instances it'll try to calll "_to_string" function and on error
|
||||
// it'll return NULL.
|
||||
static inline String* _varToString(PKVM* vm, Var self) {
|
||||
if (IS_OBJ_TYPE(self, OBJ_INST)) {
|
||||
|
||||
// The closure is retrieved from [self] thus, it doesn't need to be push
|
||||
// on the VM's temp references (since [self] should already be protected
|
||||
// from GC).
|
||||
Closure* closure = NULL;
|
||||
|
||||
String* name = newString(vm, LITS__str); // TODO: static vm string?.
|
||||
vmPushTempRef(vm, &name->_super); // method.
|
||||
bool has_method = hasMethod(vm, self, name, &closure);
|
||||
vmPopTempRef(vm); // method.
|
||||
|
||||
if (has_method) {
|
||||
Var ret = VAR_NULL;
|
||||
PkResult result = vmCallMethod(vm, self, closure, 0, NULL, &ret);
|
||||
if (result != PK_RESULT_SUCCESS) return NULL;
|
||||
|
||||
if (!IS_OBJ_TYPE(ret, OBJ_STRING)) {
|
||||
VM_SET_ERROR(vm, newString(vm, "method " LITS__str " returned "
|
||||
"non-string type."));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (String*)AS_OBJ(ret);
|
||||
}
|
||||
|
||||
// If we reached here, it doesn't have a to string override. just
|
||||
// "fall throught" and call 'toString()' bellow.
|
||||
}
|
||||
|
||||
return toString(vm, self);
|
||||
}
|
||||
|
||||
// Calls a unary operator overload method. If the method does not exists it'll
|
||||
// return false, otherwise it'll call the method and return true. If any error
|
||||
// occures it'll set an error.
|
||||
static inline bool _callUnaryOpMethod(PKVM* vm, Var self,
|
||||
const char* method_name, Var* ret) {
|
||||
Closure* closure = NULL;
|
||||
String* name = newString(vm, method_name);
|
||||
vmPushTempRef(vm, &name->_super); // method.
|
||||
bool has_method = hasMethod(vm, self, name, &closure);
|
||||
vmPopTempRef(vm); // method.
|
||||
|
||||
if (!has_method) return false;
|
||||
|
||||
vmCallMethod(vm, self, closure, 0, NULL, ret);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calls a binary operator overload method. If the method does not exists it'll
|
||||
// return false, otherwise it'll call the method and return true. If any error
|
||||
// occures it'll set an error.
|
||||
static inline bool _callBinaryOpMethod(PKVM* vm, Var self, Var other,
|
||||
const char* method_name, Var* ret) {
|
||||
Closure* closure = NULL;
|
||||
String* name = newString(vm, method_name);
|
||||
vmPushTempRef(vm, &name->_super); // method.
|
||||
bool has_method = hasMethod(vm, self, name, &closure);
|
||||
vmPopTempRef(vm); // method.
|
||||
|
||||
if (!has_method) return false;
|
||||
|
||||
vmCallMethod(vm, self, closure, 1, &other, ret);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* CORE BUILTIN FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
@ -215,13 +290,16 @@ DEF(coreAssert,
|
||||
|
||||
if (argc == 2) {
|
||||
if (AS_OBJ(ARG(2))->type != OBJ_STRING) {
|
||||
msg = toString(vm, ARG(2));
|
||||
msg = _varToString(vm, ARG(2));
|
||||
if (msg == NULL) return; //< Error at _to_string override.
|
||||
|
||||
} else {
|
||||
msg = (String*)AS_OBJ(ARG(2));
|
||||
}
|
||||
vmPushTempRef(vm, &msg->_super);
|
||||
|
||||
vmPushTempRef(vm, &msg->_super); // msg.
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Assertion failed: '@'.", msg));
|
||||
vmPopTempRef(vm);
|
||||
vmPopTempRef(vm); // msg.
|
||||
} else {
|
||||
VM_SET_ERROR(vm, newString(vm, "Assertion failed."));
|
||||
}
|
||||
@ -302,10 +380,12 @@ DEF(coreYield,
|
||||
}
|
||||
|
||||
DEF(coreToString,
|
||||
"to_string(value:var) -> string\n"
|
||||
"str(value:var) -> string\n"
|
||||
"Returns the string representation of the value.") {
|
||||
|
||||
RET(VAR_OBJ(toString(vm, ARG(1))));
|
||||
String* str = _varToString(vm, ARG(1));
|
||||
if (str == NULL) RET(VAR_NULL);
|
||||
RET(VAR_OBJ(str));
|
||||
}
|
||||
|
||||
DEF(coreChr,
|
||||
@ -348,7 +428,9 @@ DEF(corePrint,
|
||||
|
||||
for (int i = 1; i <= ARGC; i++) {
|
||||
if (i != 1) vm->config.stdout_write(vm, " ");
|
||||
vm->config.stdout_write(vm, toString(vm, ARG(i))->data);
|
||||
String* str = _varToString(vm, ARG(i));
|
||||
if (str == NULL) RET(VAR_NULL);
|
||||
vm->config.stdout_write(vm, str->data);
|
||||
}
|
||||
|
||||
vm->config.stdout_write(vm, "\n");
|
||||
@ -368,7 +450,9 @@ DEF(coreInput,
|
||||
if (vm->config.stdin_read == NULL) return;
|
||||
|
||||
if (argc == 1) {
|
||||
vm->config.stdout_write(vm, toString(vm, ARG(1))->data);
|
||||
String* str = _varToString(vm, ARG(1));
|
||||
if (str == NULL) RET(VAR_NULL);
|
||||
vm->config.stdout_write(vm, str->data);
|
||||
}
|
||||
|
||||
PkStringPtr result = vm->config.stdin_read(vm);
|
||||
@ -452,9 +536,10 @@ DEF(coreListJoin,
|
||||
pkByteBufferInit(&buff);
|
||||
|
||||
for (uint32_t i = 0; i < list->elements.count; i++) {
|
||||
String* elem = toString(vm, list->elements.data[i]);
|
||||
vmPushTempRef(vm, &elem->_super); // elem
|
||||
pkByteBufferAddString(&buff, vm, elem->data, elem->length);
|
||||
String* str = _varToString(vm, list->elements.data[i]);
|
||||
if (str == NULL) RET(VAR_NULL);
|
||||
vmPushTempRef(vm, &str->_super); // elem
|
||||
pkByteBufferAddString(&buff, vm, str->data, str->length);
|
||||
vmPopTempRef(vm); // elem
|
||||
}
|
||||
|
||||
@ -500,7 +585,7 @@ static void initializeBuiltinFunctions(PKVM* vm) {
|
||||
INITIALIZE_BUILTIN_FN("bin", coreBin, 1);
|
||||
INITIALIZE_BUILTIN_FN("hex", coreHex, 1);
|
||||
INITIALIZE_BUILTIN_FN("yield", coreYield, -1);
|
||||
INITIALIZE_BUILTIN_FN("to_string", coreToString, 1);
|
||||
INITIALIZE_BUILTIN_FN("str", coreToString, 1);
|
||||
INITIALIZE_BUILTIN_FN("chr", coreChr, 1);
|
||||
INITIALIZE_BUILTIN_FN("ord", coreOrd, 1);
|
||||
INITIALIZE_BUILTIN_FN("print", corePrint, -1);
|
||||
@ -625,7 +710,8 @@ DEF(stdLangWrite,
|
||||
if (IS_OBJ_TYPE(arg, OBJ_STRING)) {
|
||||
str = (String*)AS_OBJ(arg);
|
||||
} else {
|
||||
str = toString(vm, arg);
|
||||
str = _varToString(vm, ARG(1));
|
||||
if (str == NULL) RET(VAR_NULL);
|
||||
}
|
||||
|
||||
vm->config.stdout_write(vm, str->data);
|
||||
@ -679,7 +765,9 @@ static void _ctorString(PKVM* vm) {
|
||||
RET(VAR_OBJ(newStringLength(vm, NULL, 0)));
|
||||
return;
|
||||
}
|
||||
RET(VAR_OBJ(toString(vm, ARG(1))));
|
||||
String* str = _varToString(vm, ARG(1));
|
||||
if (str == NULL) RET(VAR_NULL);
|
||||
RET(VAR_OBJ(str));
|
||||
}
|
||||
|
||||
static void _ctorList(PKVM* vm) {
|
||||
@ -880,229 +968,271 @@ Class* getClass(PKVM* vm, Var instance) {
|
||||
return inst->cls;
|
||||
}
|
||||
|
||||
Var getMethod(PKVM* vm, Var self, String* name, bool* is_method) {
|
||||
|
||||
bool hasMethod(PKVM* vm, Var self, String* name, Closure** _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);
|
||||
Closure* method_ = cls_->methods.data[i];
|
||||
if (IS_CSTR_EQ(name, method_->fn->name, name->length)) {
|
||||
if (_method) *_method = method_;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
cls_ = cls_->super_class;
|
||||
} while (cls_ != NULL);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Var getMethod(PKVM* vm, Var self, String* name, bool* is_method) {
|
||||
|
||||
Closure* method;
|
||||
if (hasMethod(vm, self, name, &method)) {
|
||||
if (is_method) *is_method = true;
|
||||
return VAR_OBJ(method);
|
||||
}
|
||||
|
||||
// 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 " \
|
||||
#define UNSUPPORTED_UNARY_OP(op) \
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Unsupported operand ($) for " \
|
||||
"unary operator " op ".", varTypeName(v)))
|
||||
|
||||
#define UNSUPPORTED_BINARY_OP(op) \
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Unsupported operand types for " \
|
||||
"operator '" op "' $ and $", varTypeName(v1), varTypeName(v2)))
|
||||
|
||||
#define RIGHT_OPERAND "Right operand"
|
||||
|
||||
Var varAdd(PKVM* vm, Var v1, Var v2) {
|
||||
double d1, d2;
|
||||
#define CHECK_NUMERIC_OP(op) \
|
||||
do { \
|
||||
double n1, n2; \
|
||||
if (isNumeric(v1, &n1)) { \
|
||||
if (validateNumeric(vm, v2, &n2, RIGHT_OPERAND)) { \
|
||||
return VAR_NUM(n1 op n2); \
|
||||
} \
|
||||
return VAR_NULL; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
if (isNumeric(v1, &d1)) {
|
||||
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM(d1 + d2);
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
#define CHECK_BITWISE_OP(op) \
|
||||
do { \
|
||||
int64_t i1, i2; \
|
||||
if (isInteger(v1, &i1)) { \
|
||||
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) { \
|
||||
return VAR_NUM((double)(i1 op i2)); \
|
||||
} \
|
||||
return VAR_NULL; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
if (IS_OBJ(v1) && IS_OBJ(v2)) {
|
||||
Object *o1 = AS_OBJ(v1), *o2 = AS_OBJ(v2);
|
||||
#define CHECK_INST_UNARY_OP(name) \
|
||||
do { \
|
||||
if (IS_OBJ_TYPE(v, OBJ_INST)) { \
|
||||
Var result; \
|
||||
if (_callUnaryOpMethod(vm, v, name, &result)) { \
|
||||
return result; \
|
||||
} \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define CHECK_INST_BINARY_OP(name) \
|
||||
do { \
|
||||
if (IS_OBJ_TYPE(v1, OBJ_INST)) { \
|
||||
Var result; \
|
||||
if (inplace) { \
|
||||
if (_callBinaryOpMethod(vm, v1, v2, name "=", &result)) { \
|
||||
return result; \
|
||||
} \
|
||||
} \
|
||||
if (_callBinaryOpMethod(vm, v1, v2, name, &result)) { \
|
||||
return result; \
|
||||
} \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
Var varPositive(PKVM* vm, Var v) {
|
||||
double n; if (isNumeric(v, &n)) return v;
|
||||
CHECK_INST_UNARY_OP("+self");
|
||||
UNSUPPORTED_UNARY_OP("unary +");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varNegative(PKVM* vm, Var v) {
|
||||
double n; if (isNumeric(v, &n)) return VAR_NUM(-AS_NUM(v));
|
||||
CHECK_INST_UNARY_OP("-self");
|
||||
UNSUPPORTED_UNARY_OP("unary -");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varNot(PKVM* vm, Var v) {
|
||||
CHECK_INST_UNARY_OP("!self");
|
||||
return VAR_BOOL(!toBool(v));
|
||||
}
|
||||
|
||||
Var varBitNot(PKVM* vm, Var v) {
|
||||
int64_t i;
|
||||
if (isInteger(v, &i)) return VAR_NUM((double)(~i));
|
||||
CHECK_INST_UNARY_OP("~self");
|
||||
UNSUPPORTED_UNARY_OP("unary ~");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varAdd(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
|
||||
CHECK_NUMERIC_OP(+);
|
||||
|
||||
if (IS_OBJ(v1)) {
|
||||
Object *o1 = AS_OBJ(v1);
|
||||
switch (o1->type) {
|
||||
|
||||
case OBJ_STRING:
|
||||
{
|
||||
case OBJ_STRING: {
|
||||
if (!IS_OBJ(v2)) break;
|
||||
Object* o2 = AS_OBJ(v2);
|
||||
if (o2->type == OBJ_STRING) {
|
||||
return VAR_OBJ(stringJoin(vm, (String*)o1, (String*)o2));
|
||||
}
|
||||
} break;
|
||||
|
||||
case OBJ_LIST:
|
||||
{
|
||||
case OBJ_LIST: {
|
||||
if (!IS_OBJ(v2)) break;
|
||||
Object* o2 = AS_OBJ(v2);
|
||||
if (o2->type == OBJ_LIST) {
|
||||
return VAR_OBJ(listJoin(vm, (List*)o1, (List*)o2));
|
||||
if (inplace) {
|
||||
pkVarBufferConcat(&((List*)o1)->elements, vm,
|
||||
&((List*)o2)->elements);
|
||||
return v1;
|
||||
} else {
|
||||
return VAR_OBJ(listAdd(vm, (List*)o1, (List*)o2));
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES("+");
|
||||
CHECK_INST_BINARY_OP("+");
|
||||
UNSUPPORTED_BINARY_OP("+");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varSubtract(PKVM* vm, Var v1, Var v2) {
|
||||
double d1, d2;
|
||||
|
||||
if (isNumeric(v1, &d1)) {
|
||||
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM(d1 - d2);
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES("-");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varMultiply(PKVM* vm, Var v1, Var v2) {
|
||||
double d1, d2;
|
||||
|
||||
if (isNumeric(v1, &d1)) {
|
||||
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM(d1 * d2);
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES("*");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varDivide(PKVM* vm, Var v1, Var v2) {
|
||||
double d1, d2;
|
||||
|
||||
if (isNumeric(v1, &d1)) {
|
||||
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM(d1 / d2);
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES("/");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varModulo(PKVM* vm, Var v1, Var v2) {
|
||||
double d1, d2;
|
||||
|
||||
if (isNumeric(v1, &d1)) {
|
||||
if (validateNumeric(vm, v2, &d2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM(fmod(d1, d2));
|
||||
Var varModulo(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
double n1, n2;
|
||||
if (isNumeric(v1, &n1)) {
|
||||
if (validateNumeric(vm, v2, &n2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM(fmod(n1, n2));
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
if (IS_OBJ_TYPE(v1, OBJ_STRING)) {
|
||||
//const String* str = (const String*)AS_OBJ(v1);
|
||||
TODO; // "fmt" % v2.
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES("%");
|
||||
CHECK_INST_BINARY_OP("%");
|
||||
UNSUPPORTED_BINARY_OP("%");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varBitAnd(PKVM* vm, Var v1, Var v2) {
|
||||
int64_t i1, i2;
|
||||
// TODO: the bellow function definitions can be written as macros.
|
||||
|
||||
if (isInteger(v1, &i1)) {
|
||||
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM((double)(i1 & i2));
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES("&");
|
||||
Var varSubtract(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
CHECK_NUMERIC_OP(-);
|
||||
CHECK_INST_BINARY_OP("-");
|
||||
UNSUPPORTED_BINARY_OP("-");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varBitOr(PKVM* vm, Var v1, Var v2) {
|
||||
int64_t i1, i2;
|
||||
|
||||
if (isInteger(v1, &i1)) {
|
||||
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM((double)(i1 | i2));
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES("|");
|
||||
Var varMultiply(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
CHECK_NUMERIC_OP(*);
|
||||
CHECK_INST_BINARY_OP("*");
|
||||
UNSUPPORTED_BINARY_OP("*");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varBitXor(PKVM* vm, Var v1, Var v2) {
|
||||
int64_t i1, i2;
|
||||
|
||||
if (isInteger(v1, &i1)) {
|
||||
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM((double)(i1 ^ i2));
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES("^");
|
||||
Var varDivide(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
CHECK_NUMERIC_OP(/);
|
||||
CHECK_INST_BINARY_OP("/");
|
||||
UNSUPPORTED_BINARY_OP("/");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varBitLshift(PKVM* vm, Var v1, Var v2) {
|
||||
int64_t i1, i2;
|
||||
|
||||
if (isInteger(v1, &i1)) {
|
||||
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM((double)(i1 << i2));
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES("<<");
|
||||
Var varBitAnd(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
CHECK_BITWISE_OP(&);
|
||||
CHECK_INST_BINARY_OP("&");
|
||||
UNSUPPORTED_BINARY_OP("&");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varBitRshift(PKVM* vm, Var v1, Var v2) {
|
||||
int64_t i1, i2;
|
||||
|
||||
if (isInteger(v1, &i1)) {
|
||||
if (validateInteger(vm, v2, &i2, RIGHT_OPERAND)) {
|
||||
return VAR_NUM((double)(i1 >> i2));
|
||||
}
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
UNSUPPORTED_OPERAND_TYPES(">>");
|
||||
Var varBitOr(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
CHECK_BITWISE_OP(|);
|
||||
CHECK_INST_BINARY_OP("|");
|
||||
UNSUPPORTED_BINARY_OP("|");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varBitNot(PKVM* vm, Var v) {
|
||||
int64_t i;
|
||||
if (!validateInteger(vm, v, &i, "Unary operand")) return VAR_NULL;
|
||||
return VAR_NUM((double)(~i));
|
||||
Var varBitXor(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
CHECK_BITWISE_OP(^);
|
||||
CHECK_INST_BINARY_OP("^");
|
||||
UNSUPPORTED_BINARY_OP("^");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
bool varGreater(Var v1, Var v2) {
|
||||
double d1, d2;
|
||||
|
||||
if (isNumeric(v1, &d1) && isNumeric(v2, &d2)) {
|
||||
return d1 > d2;
|
||||
}
|
||||
|
||||
TODO;
|
||||
return false;
|
||||
Var varBitLshift(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
CHECK_BITWISE_OP(<<);
|
||||
CHECK_INST_BINARY_OP("<<");
|
||||
UNSUPPORTED_BINARY_OP("<<");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
bool varLesser(Var v1, Var v2) {
|
||||
double d1, d2;
|
||||
Var varBitRshift(PKVM* vm, Var v1, Var v2, bool inplace) {
|
||||
CHECK_BITWISE_OP(>>);
|
||||
CHECK_INST_BINARY_OP(">>");
|
||||
UNSUPPORTED_BINARY_OP(">>");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
if (isNumeric(v1, &d1) && isNumeric(v2, &d2)) {
|
||||
return d1 < d2;
|
||||
Var varEqals(PKVM* vm, Var v1, Var v2) {
|
||||
const bool inplace = false;
|
||||
CHECK_INST_BINARY_OP("==");
|
||||
return VAR_BOOL(isValuesEqual(v1, v2));
|
||||
}
|
||||
|
||||
Var varGreater(PKVM* vm, Var v1, Var v2) {
|
||||
CHECK_NUMERIC_OP(>);
|
||||
const bool inplace = false;
|
||||
CHECK_INST_BINARY_OP(">");
|
||||
UNSUPPORTED_BINARY_OP(">");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varLesser(PKVM* vm, Var v1, Var v2) {
|
||||
CHECK_NUMERIC_OP(<);
|
||||
const bool inplace = false;
|
||||
CHECK_INST_BINARY_OP("<");
|
||||
UNSUPPORTED_BINARY_OP("<");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
Var varOpRange(PKVM* vm, Var v1, Var v2) {
|
||||
if (IS_NUM(v1) && IS_NUM(v2)) {
|
||||
return VAR_OBJ(newRange(vm, AS_NUM(v1), AS_NUM(v2)));
|
||||
}
|
||||
|
||||
TODO;
|
||||
return false;
|
||||
const bool inplace = false;
|
||||
CHECK_INST_BINARY_OP("..");
|
||||
UNSUPPORTED_BINARY_OP("..");
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
#undef RIGHT_OPERAND
|
||||
#undef UNSUPPORTED_OPERAND_TYPES
|
||||
#undef CHECK_NUMERIC_OP
|
||||
#undef CHECK_BITWISE_OP
|
||||
#undef UNSUPPORTED_UNARY_OP
|
||||
#undef UNSUPPORTED_BINARY_OP
|
||||
|
||||
bool varContains(PKVM* vm, Var elem, Var container) {
|
||||
if (!IS_OBJ(container)) {
|
||||
@ -1140,6 +1270,13 @@ bool varContains(PKVM* vm, Var elem, Var container) {
|
||||
} break;
|
||||
}
|
||||
|
||||
#define v1 container
|
||||
#define v2 elem
|
||||
const bool inplace = false;
|
||||
CHECK_INST_BINARY_OP("in");
|
||||
#undef v1
|
||||
#undef v2
|
||||
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Argument of type $ is not iterable.",
|
||||
varTypeName(container)));
|
||||
return VAR_NULL;
|
||||
@ -1276,10 +1413,28 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
break;
|
||||
|
||||
case OBJ_INST: {
|
||||
Var value;
|
||||
if (instGetAttrib(vm, (Instance*)obj, attrib, &value)) {
|
||||
return value;
|
||||
Instance* inst = (Instance*)obj;
|
||||
Var value = VAR_NULL;
|
||||
|
||||
if (inst->native != NULL) {
|
||||
|
||||
Closure* getter;
|
||||
// TODO: static vm string?
|
||||
String* getter_name = newString(vm, GETTER_NAME);
|
||||
vmPushTempRef(vm, &getter_name->_super); // getter_name.
|
||||
bool has_getter = hasMethod(vm, on, getter_name, &getter);
|
||||
vmPopTempRef(vm); // getter_name.
|
||||
|
||||
if (has_getter) {
|
||||
Var attrib_name = VAR_OBJ(attrib);
|
||||
vmCallMethod(vm, on, getter, 1, &attrib_name, &value);
|
||||
return value; // If any error occure, it was already set.
|
||||
}
|
||||
}
|
||||
|
||||
value = mapGet(inst->attribs, VAR_OBJ(attrib));
|
||||
if (!IS_UNDEF(value)) return value;
|
||||
|
||||
} break;
|
||||
}
|
||||
|
||||
@ -1371,16 +1526,32 @@ do { \
|
||||
return;
|
||||
|
||||
case OBJ_INST: {
|
||||
if (!instSetAttrib(vm, (Instance*)obj, attrib, value)) {
|
||||
// If we has error by now, that means the set value type is
|
||||
// incompatible. No need for us to set an other error, just return.
|
||||
if (VM_HAS_ERROR(vm)) return;
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
|
||||
Instance* inst = (Instance*)obj;
|
||||
if (inst->native != NULL) {
|
||||
Closure* setter;
|
||||
// TODO: static vm string?
|
||||
String* setter_name = newString(vm, SETTER_NAME);
|
||||
vmPushTempRef(vm, &setter_name->_super); // setter_name.
|
||||
bool has_setter = hasMethod(vm, VAR_OBJ(inst), setter_name, &setter);
|
||||
vmPopTempRef(vm); // setter_name.
|
||||
|
||||
if (has_setter) {
|
||||
|
||||
// FIXME:
|
||||
// Once we retreive values from directly the stack we can pass the
|
||||
// args pointer, pointing in the VM stack, instead of creating a temp
|
||||
// array as bellow.
|
||||
Var args[2] = { VAR_OBJ(attrib), value };
|
||||
|
||||
vmCallMethod(vm, VAR_OBJ(inst), setter, 2, args, NULL);
|
||||
return; // If any error occure, it was already set.
|
||||
}
|
||||
}
|
||||
|
||||
// If we reached here, that means the attribute exists and we have
|
||||
// updated the value.
|
||||
mapSet(vm, inst->attribs, VAR_OBJ(attrib), value);
|
||||
return;
|
||||
|
||||
} break;
|
||||
}
|
||||
|
||||
@ -1429,14 +1600,15 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
|
||||
Var value = mapGet((Map*)obj, key);
|
||||
if (IS_UNDEF(value)) {
|
||||
|
||||
String* key_str = toString(vm, key);
|
||||
vmPushTempRef(vm, &key_str->_super);
|
||||
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Invalid key '@'.", key_str));
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Unhashable key '$'.",
|
||||
varTypeName(key)));
|
||||
} else {
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Key '@' not exists", key_str));
|
||||
String* key_repr = toRepr(vm, key);
|
||||
vmPushTempRef(vm, &key_repr->_super); // key_repr.
|
||||
VM_SET_ERROR(vm, stringFormat(vm, "Key '@' not exists", key_repr));
|
||||
vmPopTempRef(vm); // key_repr.
|
||||
}
|
||||
vmPopTempRef(vm);
|
||||
return VAR_NULL;
|
||||
}
|
||||
return value;
|
||||
|
@ -10,6 +10,35 @@
|
||||
#include "pk_internal.h"
|
||||
#include "pk_value.h"
|
||||
|
||||
// Literal strings used in various places in pocketlang. For now these are
|
||||
// defined as macros so that it'll be easier in the future to refactor or
|
||||
// restructre. The names of the macros are begin with LIST_ and the string.
|
||||
#define LITS__init "_init"
|
||||
#define LITS__str "_str"
|
||||
|
||||
// Functions, methods, classes and other names which are intrenal / special to
|
||||
// pocketlang are starts with the following character (ex: @main, @literalFn).
|
||||
// When importing all (*) from a module, if the name of an entry starts with
|
||||
// this character it'll be skipped.
|
||||
#define SPECIAL_NAME_CHAR '@'
|
||||
|
||||
// Name of the implicit function for a module. When a module is parsed all of
|
||||
// it's statements are wrapped around an implicit function with this name.
|
||||
#define IMPLICIT_MAIN_NAME "@main"
|
||||
|
||||
// Name of a literal function. All literal function will have the same name but
|
||||
// they're uniquely identified by their index in the script's function buffer.
|
||||
#define LITERAL_FN_NAME "@func"
|
||||
|
||||
// Name of a constructor function.
|
||||
#define CTOR_NAME LITS__init
|
||||
|
||||
// Getter/Setter method names used by the native instance to get/ set value.
|
||||
// Script instance's values doesn't support methods but they use vanila
|
||||
// '.attrib', '.attrib=' operators.
|
||||
#define GETTER_NAME "@getter"
|
||||
#define SETTER_NAME "@setter"
|
||||
|
||||
// Initialize core language, builtin function and core libs.
|
||||
void initializeCore(PKVM* vm);
|
||||
|
||||
@ -46,21 +75,33 @@ Class* getClass(PKVM* vm, Var instance);
|
||||
// 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.
|
||||
Var varDivide(PKVM* vm, Var v1, Var v2); // Returns v1 / v2.
|
||||
Var varModulo(PKVM* vm, Var v1, Var v2); // Returns v1 % v2.
|
||||
// Unlike getMethod this will not set error and will not try to get attribute
|
||||
// with the same name. It'll return true if the method exists on [self], false
|
||||
// otherwise and if the [method] argument is not NULL, method will be set.
|
||||
bool hasMethod(PKVM* vm, Var self, String* name, Closure** method);
|
||||
|
||||
Var varBitAnd(PKVM* vm, Var v1, Var v2); // Returns v1 & v2.
|
||||
Var varBitOr(PKVM* vm, Var v1, Var v2); // Returns v1 | v2.
|
||||
Var varBitXor(PKVM* vm, Var v1, Var v2); // Returns v1 ^ v2.
|
||||
Var varBitLshift(PKVM* vm, Var v1, Var v2); // Returns v1 << v2.
|
||||
Var varBitRshift(PKVM* vm, Var v1, Var v2); // Returns v1 >> v2.
|
||||
Var varBitNot(PKVM* vm, Var v); // Returns ~v.
|
||||
Var varPositive(PKVM* vm, Var v); // Returns +v.
|
||||
Var varNegative(PKVM* vm, Var v); // Returns -v.
|
||||
Var varNot(PKVM* vm, Var v); // Returns !v.
|
||||
Var varBitNot(PKVM* vm, Var v); // Returns ~v.
|
||||
|
||||
bool varGreater(Var v1, Var v2); // Returns v1 > v2.
|
||||
bool varLesser(Var v1, Var v2); // Returns v1 < v2.
|
||||
Var varAdd(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 + v2.
|
||||
Var varSubtract(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 - v2.
|
||||
Var varMultiply(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 * v2.
|
||||
Var varDivide(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 / v2.
|
||||
Var varModulo(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 % v2.
|
||||
|
||||
Var varBitAnd(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 & v2.
|
||||
Var varBitOr(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 | v2.
|
||||
Var varBitXor(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 ^ v2.
|
||||
Var varBitLshift(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 << v2.
|
||||
Var varBitRshift(PKVM* vm, Var v1, Var v2, bool inplace); // Returns v1 >> v2.
|
||||
|
||||
Var varEqals(PKVM* vm, Var v1, Var v2); // Returns v1 == v2.
|
||||
Var varGreater(PKVM* vm, Var v1, Var v2); // Returns v1 > v2.
|
||||
Var varLesser(PKVM* vm, Var v1, Var v2); // Returns v1 < v2.
|
||||
|
||||
Var varOpRange(PKVM* vm, Var v1, Var v2); // Returns v1 .. v2.
|
||||
|
||||
// Returns [elem] in [container]. Sets an error if the [container] is not an
|
||||
// iterable.
|
||||
|
@ -269,11 +269,17 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
uint32_t* lines = func->fn->oplines.data;
|
||||
uint32_t line = 1, last_line = 0;
|
||||
|
||||
// Either path or name should be valid to a module.
|
||||
ASSERT(func->owner->path != NULL || func->owner->name != NULL, OOPS);
|
||||
const char* path = (func->owner->path)
|
||||
? func->owner->path->data
|
||||
: func->owner->name->data;
|
||||
|
||||
// This will print: Instruction Dump of function 'fn' "path.pk"\n
|
||||
PRINT("Instruction Dump of function ");
|
||||
PRINT(func->name);
|
||||
PRINT(" ");
|
||||
PRINT(func->owner->path->data);
|
||||
PRINT(path);
|
||||
NEWLINE();
|
||||
|
||||
while (i < func->fn->opcodes.count) {
|
||||
@ -588,9 +594,11 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
NO_ARGS();
|
||||
break;
|
||||
|
||||
case OP_POSITIVE:
|
||||
case OP_NEGATIVE:
|
||||
case OP_NOT:
|
||||
case OP_BIT_NOT:
|
||||
|
||||
case OP_ADD:
|
||||
case OP_SUBTRACT:
|
||||
case OP_MULTIPLY:
|
||||
@ -601,6 +609,17 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
case OP_BIT_XOR:
|
||||
case OP_BIT_LSHIFT:
|
||||
case OP_BIT_RSHIFT:
|
||||
{
|
||||
uint8_t inplace = READ_BYTE();
|
||||
if (inplace == 1) {
|
||||
PRINT("(inplace)\n");
|
||||
} else {
|
||||
PRINT("\n");
|
||||
ASSERT(inplace == 0, "inplace should be either 0 or 1");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_EQEQ:
|
||||
case OP_NOTEQ:
|
||||
case OP_LT:
|
||||
|
@ -68,20 +68,6 @@
|
||||
// The size of the error message buffer, used ar vsnprintf (since c99) buffer.
|
||||
#define ERROR_MESSAGE_SIZE 512
|
||||
|
||||
// Functions, methods, classes and other names which are intrenal / special to
|
||||
// pocketlang are starts with the following character (ex: @main, @literalFn).
|
||||
// When importing all (*) from a module, if the name of an entry starts with
|
||||
// this character it'll be skipped.
|
||||
#define SPECIAL_NAME_CHAR '@'
|
||||
|
||||
// Name of the implicit function for a module. When a module is parsed all of
|
||||
// it's statements are wrapped around an implicit function with this name.
|
||||
#define IMPLICIT_MAIN_NAME "@main"
|
||||
|
||||
// Name of a literal function. All literal function will have the same name but
|
||||
// they're uniquely identified by their index in the script's function buffer.
|
||||
#define LITERAL_FN_NAME "@func"
|
||||
|
||||
/*****************************************************************************/
|
||||
/* ALLOCATION MACROS */
|
||||
/*****************************************************************************/
|
||||
|
@ -224,22 +224,24 @@ OPCODE(GET_SUBSCRIPT_KEEP, 0, 1)
|
||||
OPCODE(SET_SUBSCRIPT, 0, -2)
|
||||
|
||||
// Pop unary operand and push value.
|
||||
OPCODE(POSITIVE, 0, 0) //< Negative number value.
|
||||
OPCODE(NEGATIVE, 0, 0) //< Negative number value.
|
||||
OPCODE(NOT, 0, 0) //< boolean not.
|
||||
OPCODE(BIT_NOT, 0, 0) //< bitwise not.
|
||||
|
||||
// Pop binary operands and push value.
|
||||
OPCODE(ADD, 0, -1)
|
||||
OPCODE(SUBTRACT, 0, -1)
|
||||
OPCODE(MULTIPLY, 0, -1)
|
||||
OPCODE(DIVIDE, 0, -1)
|
||||
OPCODE(MOD, 0, -1)
|
||||
// for parameter 1 byte is boolean inplace?.
|
||||
OPCODE(ADD, 1, -1)
|
||||
OPCODE(SUBTRACT, 1, -1)
|
||||
OPCODE(MULTIPLY, 1, -1)
|
||||
OPCODE(DIVIDE, 1, -1)
|
||||
OPCODE(MOD, 1, -1)
|
||||
|
||||
OPCODE(BIT_AND, 0, -1)
|
||||
OPCODE(BIT_OR, 0, -1)
|
||||
OPCODE(BIT_XOR, 0, -1)
|
||||
OPCODE(BIT_LSHIFT, 0, -1)
|
||||
OPCODE(BIT_RSHIFT, 0, -1)
|
||||
OPCODE(BIT_AND, 1, -1)
|
||||
OPCODE(BIT_OR, 1, -1)
|
||||
OPCODE(BIT_XOR, 1, -1)
|
||||
OPCODE(BIT_LSHIFT, 1, -1)
|
||||
OPCODE(BIT_RSHIFT, 1, -1)
|
||||
|
||||
OPCODE(EQEQ, 0, -1)
|
||||
OPCODE(NOTEQ, 0, -1)
|
||||
|
@ -207,10 +207,16 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls,
|
||||
CHECK_ARG_NULL(fptr);
|
||||
CHECK_HANDLE_TYPE(cls, OBJ_CLASS);
|
||||
|
||||
// TODO:
|
||||
// Check if the method name is valid, and validate argc for special
|
||||
// methods (like "@getter", "@call", "+", "-", etc).
|
||||
|
||||
Class* class_ = (Class*)AS_OBJ(cls->value);
|
||||
|
||||
Function* fn = newFunction(vm, name, (int)strlen(name),
|
||||
class_->owner, true, NULL, NULL);
|
||||
fn->arity = arity;
|
||||
fn->native = fptr;
|
||||
|
||||
// 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
|
||||
@ -218,8 +224,7 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls,
|
||||
|
||||
Closure* method = newClosure(vm, fn);
|
||||
|
||||
// FIXME: name "_init" is literal everywhere.
|
||||
if (strcmp(name, "_init") == 0) {
|
||||
if (strcmp(name, CTOR_NAME) == 0) {
|
||||
class_->ctor = method;
|
||||
|
||||
} else {
|
||||
@ -344,6 +349,7 @@ int pkGetArgc(const PKVM* vm) {
|
||||
}
|
||||
|
||||
bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max) {
|
||||
CHECK_RUNTIME();
|
||||
ASSERT(min <= max, "invalid argc range (min > max).");
|
||||
|
||||
if (argc < min) {
|
||||
|
@ -389,6 +389,8 @@ Upvalue* newUpvalue(PKVM* vm, Var* value) {
|
||||
}
|
||||
|
||||
Fiber* newFiber(PKVM* vm, Closure* closure) {
|
||||
ASSERT(closure->fn->arity >= -1, OOPS);
|
||||
|
||||
Fiber* fiber = ALLOCATE(vm, Fiber);
|
||||
|
||||
// Not sure why this memset is needed here. If it doesn't then remove it.
|
||||
@ -404,6 +406,10 @@ Fiber* newFiber(PKVM* vm, Closure* closure) {
|
||||
// there won't be any locals or temps (which are belongs to the
|
||||
// native "C" stack).
|
||||
int stack_size = utilPowerOf2Ceil(closure->fn->arity + 1);
|
||||
|
||||
// We need at least 1 stack slot for the return value.
|
||||
if (stack_size == 0) stack_size++;
|
||||
|
||||
fiber->stack = ALLOCATE_ARRAY(vm, Var, stack_size);
|
||||
fiber->stack_size = stack_size;
|
||||
fiber->ret = fiber->stack;
|
||||
@ -697,7 +703,7 @@ Var listRemoveAt(PKVM* vm, List* self, uint32_t index) {
|
||||
return removed;
|
||||
}
|
||||
|
||||
List* listJoin(PKVM* vm, List* l1, List* l2) {
|
||||
List* listAdd(PKVM* vm, List* l1, List* l2) {
|
||||
|
||||
// Optimize end case.
|
||||
if (l1->elements.count == 0) return l2;
|
||||
@ -706,10 +712,10 @@ List* listJoin(PKVM* vm, List* l1, List* l2) {
|
||||
uint32_t size = l1->elements.count + l2->elements.count;
|
||||
List* list = newList(vm, size);
|
||||
|
||||
vmPushTempRef(vm, &list->_super);
|
||||
vmPushTempRef(vm, &list->_super); // list.
|
||||
pkVarBufferConcat(&list->elements, vm, &l1->elements);
|
||||
pkVarBufferConcat(&list->elements, vm, &l2->elements);
|
||||
vmPopTempRef(vm);
|
||||
vmPopTempRef(vm); // list.
|
||||
|
||||
return list;
|
||||
}
|
||||
@ -1080,38 +1086,6 @@ void moduleAddMain(PKVM* vm, Module* module) {
|
||||
VAR_OBJ(module->body));
|
||||
}
|
||||
|
||||
bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value) {
|
||||
ASSERT((inst != NULL) && (attrib != NULL) && (value != NULL), OOPS);
|
||||
|
||||
if (inst->native != NULL) {
|
||||
TODO;
|
||||
}
|
||||
|
||||
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->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;
|
||||
|
||||
// FIXME:
|
||||
// Only return true if attribute have been set.
|
||||
return true;
|
||||
}
|
||||
|
||||
mapSet(vm, inst->attribs, VAR_OBJ(attrib), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* UTILITY FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
@ -1222,6 +1196,11 @@ bool isValuesSame(Var v1, Var v2) {
|
||||
bool isValuesEqual(Var v1, Var v2) {
|
||||
if (isValuesSame(v1, v2)) return true;
|
||||
|
||||
// +0 and -0 have different bit value representations.
|
||||
if (IS_NUM(v1) && IS_NUM(v2)) {
|
||||
return AS_NUM(v1) == AS_NUM(v2);
|
||||
}
|
||||
|
||||
// If we reach here only heap allocated objects could be compared.
|
||||
if (!IS_OBJ(v1) || !IS_OBJ(v2)) return false;
|
||||
|
||||
|
@ -665,7 +665,7 @@ void listInsert(PKVM* vm, List* self, uint32_t index, Var value);
|
||||
Var listRemoveAt(PKVM* vm, List* self, uint32_t index);
|
||||
|
||||
// Create a new list by joining the 2 given list and return the result.
|
||||
List* listJoin(PKVM* vm, List* l1, List* l2);
|
||||
List* listAdd(PKVM* vm, List* l1, List* l2);
|
||||
|
||||
// Returns the value for the [key] in the map. If key not exists return
|
||||
// VAR_UNDEFINED.
|
||||
@ -718,16 +718,6 @@ void moduleSetGlobal(Module* module, int index, Var value);
|
||||
// function.
|
||||
void moduleAddMain(PKVM* vm, Module* module);
|
||||
|
||||
// Get the attribut from the instance and set it [value]. On success return
|
||||
// true, if the attribute not exists it'll return false but won't set an error.
|
||||
bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value);
|
||||
|
||||
// Set the attribute to the instance and return true on success, if the
|
||||
// attribute doesn't exists it'll return false but if the [value] type is
|
||||
// incompatible, this will set an error to the VM, which you can check with
|
||||
// VM_HAS_ERROR() macro function.
|
||||
bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value);
|
||||
|
||||
// Release all the object owned by the [self] including itself.
|
||||
void freeObject(PKVM* vm, Object* self);
|
||||
|
||||
|
211
src/pk_vm.c
211
src/pk_vm.c
@ -173,15 +173,10 @@ bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var* argv) {
|
||||
|
||||
ASSERT(fiber->stack != NULL && fiber->sp == fiber->stack + 1, OOPS);
|
||||
ASSERT(fiber->ret == fiber->stack, OOPS);
|
||||
ASSERT((fiber->stack + fiber->stack_size) - fiber->sp >= argc, OOPS);
|
||||
|
||||
// Pass the function arguments.
|
||||
|
||||
// Assert we have the first frame (to push the arguments). And assert we have
|
||||
// enough stack space for parameters.
|
||||
ASSERT(fiber->frame_count == 1, OOPS);
|
||||
ASSERT(fiber->frames[0].rbp == fiber->ret, OOPS);
|
||||
ASSERT((fiber->stack + fiber->stack_size) - fiber->sp >= argc, OOPS);
|
||||
|
||||
// ARG1 is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc).
|
||||
// And ret[0] is the return value, parameters starts at ret[1], ...
|
||||
for (int i = 0; i < argc; i++) {
|
||||
@ -189,6 +184,18 @@ bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var* argv) {
|
||||
}
|
||||
fiber->sp += argc; // Parameters.
|
||||
|
||||
// Native functions doesn't own a stack frame so, we're done here.
|
||||
if (fiber->closure->fn->is_native) return true;
|
||||
|
||||
// Assert we have the first frame (to push the arguments). And assert we have
|
||||
// enough stack space for parameters.
|
||||
ASSERT(fiber->frame_count == 1, OOPS);
|
||||
ASSERT(fiber->frames[0].rbp == fiber->ret, OOPS);
|
||||
|
||||
// Capture self.
|
||||
fiber->frames[0].self = fiber->self;
|
||||
fiber->self = VAR_UNDEFINED;
|
||||
|
||||
// On success return true.
|
||||
return true;
|
||||
}
|
||||
@ -245,18 +252,39 @@ void vmYieldFiber(PKVM* vm, Var* value) {
|
||||
vm->fiber = caller;
|
||||
}
|
||||
|
||||
PkResult vmRunFunction(PKVM* vm, Closure* fn, int argc, Var* argv, Var* ret) {
|
||||
PkResult vmCallMethod(PKVM* vm, Var self, Closure* fn,
|
||||
int argc, Var* argv, Var* ret) {
|
||||
ASSERT(argc >= 0, "argc cannot be negative.");
|
||||
ASSERT(argc == 0 || argv != NULL, "argv was NULL when argc > 0.");
|
||||
|
||||
Fiber* fiber = newFiber(vm, fn);
|
||||
fiber->self = self;
|
||||
vmPushTempRef(vm, &fiber->_super); // fiber.
|
||||
vmPrepareFiber(vm, fiber, argc, argv);
|
||||
bool success = vmPrepareFiber(vm, fiber, argc, argv);
|
||||
vmPopTempRef(vm); // fiber.
|
||||
if (!success) return PK_RESULT_RUNTIME_ERROR;
|
||||
|
||||
PkResult result;
|
||||
|
||||
Fiber* last = vm->fiber;
|
||||
if (last != NULL) vmPushTempRef(vm, &last->_super); // last.
|
||||
PkResult result = vmRunFiber(vm, fiber);
|
||||
{
|
||||
if (fiber->closure->fn->is_native) {
|
||||
|
||||
ASSERT(fiber->closure->fn->native != NULL, "Native function was NULL");
|
||||
vm->fiber = fiber;
|
||||
fiber->closure->fn->native(vm);
|
||||
if (VM_HAS_ERROR(vm)) {
|
||||
if (last != NULL) last->error = vm->fiber->error;
|
||||
result = PK_RESULT_RUNTIME_ERROR;
|
||||
} else {
|
||||
result = PK_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
} else {
|
||||
result = vmRunFiber(vm, fiber);
|
||||
}
|
||||
}
|
||||
if (last != NULL) vmPopTempRef(vm); // last.
|
||||
vm->fiber = last;
|
||||
|
||||
@ -265,6 +293,13 @@ PkResult vmRunFunction(PKVM* vm, Closure* fn, int argc, Var* argv, Var* ret) {
|
||||
return result;
|
||||
}
|
||||
|
||||
PkResult vmRunFunction(PKVM* vm, Closure* fn, int argc, Var* argv, Var* ret) {
|
||||
|
||||
// Calling functions and methods are the same, except for the methods have
|
||||
// self defined, and for functions it'll be VAR_UNDEFINED.
|
||||
return vmCallMethod(vm, VAR_UNDEFINED, fn, argc, argv, ret);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* VM INTERNALS */
|
||||
/*****************************************************************************/
|
||||
@ -612,7 +647,7 @@ L_vm_main_loop:
|
||||
DEBUG_BREAK(); \
|
||||
} while (false)
|
||||
|
||||
#if DUMP_STACK
|
||||
#if DUMP_STACK && defined(DEBUG)
|
||||
_DUMP_STACK();
|
||||
#endif
|
||||
#undef _DUMP_STACK
|
||||
@ -852,8 +887,7 @@ L_vm_main_loop:
|
||||
Closure* method = (Closure*)AS_OBJ(PEEK(-1));
|
||||
Class* cls = (Class*)AS_OBJ(PEEK(-2));
|
||||
|
||||
// FIXME: literal string "_inint".
|
||||
if (strcmp(method->fn->name, "_init") == 0) {
|
||||
if (strcmp(method->fn->name, CTOR_NAME) == 0) {
|
||||
cls->ctor = method;
|
||||
} else {
|
||||
// TODO: The method buffer should be ordered with it's name and
|
||||
@ -1318,30 +1352,48 @@ L_do_call:
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(POSITIVE):
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var self = PEEK(-1);
|
||||
Var result = varPositive(vm, self);
|
||||
DROP(); // self
|
||||
PUSH(result);
|
||||
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(NEGATIVE):
|
||||
{
|
||||
Var num = POP();
|
||||
if (!IS_NUM(num)) {
|
||||
RUNTIME_ERROR(newString(vm, "Can not negate a non numeric value."));
|
||||
}
|
||||
PUSH(VAR_NUM(-AS_NUM(num)));
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var self = PEEK(-1);
|
||||
Var result = varNegative(vm, self);
|
||||
DROP(); // self
|
||||
PUSH(result);
|
||||
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(NOT):
|
||||
{
|
||||
Var val = POP();
|
||||
PUSH(VAR_BOOL(!toBool(val)));
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var self = PEEK(-1);
|
||||
Var result = varNot(vm, self);
|
||||
DROP(); // self
|
||||
PUSH(result);
|
||||
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(BIT_NOT):
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var val = PEEK(-1);
|
||||
|
||||
Var result = varBitNot(vm, val);
|
||||
DROP(); // val
|
||||
Var self = PEEK(-1);
|
||||
Var result = varBitNot(vm, self);
|
||||
DROP(); // self
|
||||
PUSH(result);
|
||||
|
||||
CHECK_ERROR();
|
||||
@ -1355,7 +1407,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varAdd(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varAdd(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1367,7 +1420,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varSubtract(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varSubtract(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1379,7 +1433,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varMultiply(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varMultiply(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1391,7 +1446,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varDivide(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varDivide(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1403,7 +1459,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varModulo(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varModulo(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1415,7 +1472,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varBitAnd(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varBitAnd(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1427,8 +1485,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
|
||||
Var result = varBitOr(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varBitOr(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1440,8 +1498,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
|
||||
Var result = varBitXor(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varBitXor(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1453,8 +1511,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
|
||||
Var result = varBitLshift(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varBitLshift(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1466,8 +1524,8 @@ L_do_call:
|
||||
{
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
|
||||
Var result = varBitRshift(vm, l, r);
|
||||
uint8_t inplace = READ_BYTE(); ASSERT(inplace <= 1, OOPS);
|
||||
Var result = varBitRshift(vm, l, r, inplace);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
|
||||
@ -1477,74 +1535,89 @@ L_do_call:
|
||||
|
||||
OPCODE(EQEQ):
|
||||
{
|
||||
Var r = POP(), l = POP();
|
||||
PUSH(VAR_BOOL(isValuesEqual(l, r)));
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varEqals(vm, l, r);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(NOTEQ):
|
||||
{
|
||||
Var r = POP(), l = POP();
|
||||
PUSH(VAR_BOOL(!isValuesEqual(l, r)));
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varEqals(vm, l, r);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(VAR_BOOL(!toBool(result)));
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(LT):
|
||||
{
|
||||
Var r = POP(), l = POP();
|
||||
PUSH(VAR_BOOL(varLesser(l, r)));
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varLesser(vm, l, r);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(LTEQ):
|
||||
{
|
||||
Var r = POP(), l = POP();
|
||||
bool lteq = varLesser(l, r);
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
|
||||
Var result = varLesser(vm, l, r);
|
||||
CHECK_ERROR();
|
||||
bool lteq = toBool(result);
|
||||
|
||||
if (!lteq) result = varEqals(vm, l, r);
|
||||
CHECK_ERROR();
|
||||
|
||||
if (!lteq) {
|
||||
lteq = isValuesEqual(l, r);
|
||||
CHECK_ERROR();
|
||||
}
|
||||
|
||||
PUSH(VAR_BOOL(lteq));
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(GT):
|
||||
{
|
||||
Var r = POP(), l = POP();
|
||||
PUSH(VAR_BOOL(varGreater(l, r)));
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varGreater(vm, l, r);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(GTEQ):
|
||||
{
|
||||
Var r = POP(), l = POP();
|
||||
bool gteq = varGreater(l, r);
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varGreater(vm, l, r);
|
||||
CHECK_ERROR();
|
||||
bool gteq = toBool(result);
|
||||
|
||||
if (!gteq) result = varEqals(vm, l, r);
|
||||
CHECK_ERROR();
|
||||
|
||||
if (!gteq) {
|
||||
gteq = isValuesEqual(l, r);
|
||||
CHECK_ERROR();
|
||||
}
|
||||
|
||||
PUSH(VAR_BOOL(gteq));
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(RANGE):
|
||||
{
|
||||
Var to = PEEK(-1); // Don't pop yet, we need the reference for gc.
|
||||
Var from = PEEK(-2); // Don't pop yet, we need the reference for gc.
|
||||
if (!IS_NUM(from) || !IS_NUM(to)) {
|
||||
RUNTIME_ERROR(newString(vm, "Range arguments must be number."));
|
||||
}
|
||||
DROP(); // to
|
||||
DROP(); // from
|
||||
PUSH(VAR_OBJ(newRange(vm, AS_NUM(from), AS_NUM(to))));
|
||||
// Don't pop yet, we need the reference for gc.
|
||||
Var r = PEEK(-1), l = PEEK(-2);
|
||||
Var result = varOpRange(vm, l, r);
|
||||
DROP(); DROP(); // r, l
|
||||
PUSH(result);
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
|
13
src/pk_vm.h
13
src/pk_vm.h
@ -221,9 +221,16 @@ void vmYieldFiber(PKVM* vm, Var* value);
|
||||
// till the next yield or return statement, and return result.
|
||||
PkResult vmRunFiber(PKVM* vm, Fiber* fiber);
|
||||
|
||||
// Runs the script function (if not an assertion will fail) and if the [ret] is
|
||||
// not null the return value will be set. [argv] should be the first argument
|
||||
// pointer following the rest of the arguments in an array.
|
||||
// Runs the function and if the [ret] is not NULL the return value will be set.
|
||||
// [argv] should be the first argument pointer following the rest of the
|
||||
// arguments in an array.
|
||||
PkResult vmRunFunction(PKVM* vm, Closure* fn, int argc, Var* argv, Var* ret);
|
||||
|
||||
// Call the method on the [self], (witch has retrieved by the getMethod()
|
||||
// function) and if the [ret] is not NULL, the return value will be set.
|
||||
// [argv] should be the first argument pointer following the rest of the
|
||||
// arguments in an array.
|
||||
PkResult vmCallMethod(PKVM* vm, Var self, Closure* fn,
|
||||
int argc, Var* argv, Var* ret);
|
||||
|
||||
#endif // PK_VM_H
|
||||
|
@ -27,6 +27,11 @@ l1 = [1] + []; assert(l1.length == 1); assert(l1[0] == 1)
|
||||
l2 = l1 + [1,2,3]; assert(l2.length == 4); assert(l2 == [1,1,2,3])
|
||||
l3 = l2 + l1 + l2; assert(l3 == [1,1,2,3,1,1,1,2,3])
|
||||
|
||||
## list references are shared.
|
||||
l1 = [1];l2 = l1;l1 += [2]
|
||||
assert(l2[1] == 2)
|
||||
assert(l1 == l2)
|
||||
|
||||
## in tests.
|
||||
assert(!('abc' in 'a'))
|
||||
assert(42 in [12, 42, 3.14])
|
||||
@ -35,7 +40,7 @@ assert('key' in {'key':'value'})
|
||||
assert(!('foo' in {'bar':'baz'}))
|
||||
|
||||
## Builtin functions tests.
|
||||
assert(to_string(42) == '42')
|
||||
assert(str(42) == '42')
|
||||
|
||||
## FIXME: add hash function.
|
||||
##h1 = math.hash("testing"); h2 = math.hash("test" + "ing")
|
||||
@ -57,13 +62,13 @@ assert((a and b) == null)
|
||||
assert((b or a) == [1, 2, 3])
|
||||
assert((a or b) == b or a)
|
||||
|
||||
## Recursive to_string list/map
|
||||
## Recursive str list/map
|
||||
l = [1]
|
||||
list_append(l, l)
|
||||
assert(to_string(l) == '[1, [...]]')
|
||||
assert(str(l) == '[1, [...]]')
|
||||
m = {}
|
||||
m['m'] = m
|
||||
assert(to_string(m) == '{"m":{...}}')
|
||||
assert(str(m) == '{"m":{...}}')
|
||||
|
||||
# Bitwise operation tests
|
||||
assert(0b1010 | 0b0101 == 0b1111)
|
||||
|
@ -1,50 +1,26 @@
|
||||
|
||||
class Vec2
|
||||
def _init(x, y)
|
||||
self.x = x
|
||||
self.y = y
|
||||
end
|
||||
###############################################################################
|
||||
## Basics
|
||||
###############################################################################
|
||||
|
||||
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()}")
|
||||
|
||||
|
||||
## Default constructor crash (issue #213)
|
||||
class A
|
||||
end
|
||||
a = A()
|
||||
print(a)
|
||||
|
||||
class B is A
|
||||
end
|
||||
|
||||
b = B()
|
||||
assert((b is B) and (b is A))
|
||||
## TODO: write some tests here.
|
||||
|
||||
###############################################################################
|
||||
## INHERITANCE
|
||||
###############################################################################
|
||||
|
||||
class A
|
||||
end
|
||||
|
||||
class B is A
|
||||
end
|
||||
|
||||
a = A()
|
||||
print(a)
|
||||
|
||||
b = B()
|
||||
assert((b is B) and (b is A))
|
||||
|
||||
class Shape
|
||||
def display()
|
||||
return "${self.name} shape"
|
||||
@ -95,6 +71,126 @@ assert(s is Rectangle)
|
||||
assert(s is Shape)
|
||||
assert(s.area() == 16)
|
||||
|
||||
###############################################################################
|
||||
## OPERATOR OVERLOADING
|
||||
###############################################################################
|
||||
|
||||
class Vec2
|
||||
def _init(x, y)
|
||||
self.x = x; self.y = y
|
||||
end
|
||||
def _str
|
||||
return "<${self.x}, ${self.y}>"
|
||||
end
|
||||
def +(other)
|
||||
return Vec2(self.x + other.x,
|
||||
self.y + other.y)
|
||||
end
|
||||
def +=(other)
|
||||
self.x += other.x
|
||||
self.y += other.y
|
||||
return self
|
||||
end
|
||||
def ==(other)
|
||||
return self.x == other.x and self.y == other.y
|
||||
end
|
||||
end
|
||||
|
||||
v1 = Vec2(1, 2); assert(v1.x == 1 and v1.y == 2)
|
||||
print("v1 = $v1")
|
||||
|
||||
assert(str(v1) == "<1, 2>")
|
||||
|
||||
v2 = Vec2(3, 4); assert(v2.x == 3 and v2.y == 4)
|
||||
print("v2 = $v2")
|
||||
|
||||
v3 = v1 + v2
|
||||
print("v3 = $v3")
|
||||
|
||||
assert(v1 == Vec2(1, 2))
|
||||
|
||||
v1 += v2
|
||||
assert(v1 == v3)
|
||||
|
||||
class Path
|
||||
def _init(path)
|
||||
self.path = path
|
||||
end
|
||||
def /(other)
|
||||
if other is String
|
||||
return Path(self.path + "/" + other)
|
||||
else if other is Path
|
||||
return Path(self.path + "/" + other.path)
|
||||
else
|
||||
assert(false, "Invalid type")
|
||||
end
|
||||
end
|
||||
def _str
|
||||
return self.path
|
||||
end
|
||||
end
|
||||
|
||||
local = Path("/usr/local")
|
||||
pocket = Path("bin/pocket")
|
||||
print('local/pocket = "${local/pocket}"')
|
||||
assert(str(local/pocket) == "/usr/local/bin/pocket")
|
||||
assert(str(local/"lib") == "/usr/local/lib")
|
||||
|
||||
class N
|
||||
def _init(val)
|
||||
self.val = val
|
||||
end
|
||||
def _str()
|
||||
return "N(${self.val})"
|
||||
end
|
||||
def +(other)
|
||||
return N(self.val + other.val)
|
||||
end
|
||||
def +=(other)
|
||||
self.val += other.val
|
||||
return self
|
||||
end
|
||||
def %=(num)
|
||||
self.val %= num
|
||||
return self
|
||||
end
|
||||
def ==(other)
|
||||
return self.val == other.val
|
||||
end
|
||||
def <<(num)
|
||||
self.val *= 10
|
||||
self.val += num
|
||||
return self
|
||||
end
|
||||
def >>(other)
|
||||
other.val = self.val % 10
|
||||
self.val = (self.val - other.val) / 10
|
||||
end
|
||||
|
||||
def !self()
|
||||
self.val -= 1
|
||||
return self.val + 1
|
||||
end
|
||||
end
|
||||
|
||||
n1 = N(12)
|
||||
n2 = N(23)
|
||||
assert(n1 + n2 == N(n1.val + n2.val))
|
||||
n1 %= 5
|
||||
assert(n1.val == 2)
|
||||
|
||||
n3 = N(4) << 8 << 3 << 6
|
||||
assert(n3 == N(4836))
|
||||
|
||||
n4 = N(0)
|
||||
n3 >> n4; assert(n4.val == 6)
|
||||
n3 >> n4; assert(n4.val == 3)
|
||||
n3 >> n4; assert(n4.val == 8)
|
||||
n3 >> n4; assert(n4.val == 4)
|
||||
|
||||
n5 = N(3)
|
||||
assert(!n5 == 3)
|
||||
assert(!n5 == 2)
|
||||
assert(!n5 == 1)
|
||||
|
||||
print('ALL TESTS PASSED')
|
||||
|
||||
|
||||
|
23
tests/modules/dummy.pk
Normal file
23
tests/modules/dummy.pk
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
## TODO: this dummy module is just to test different aspects of the
|
||||
## native module interface and will be removed once it become stable
|
||||
## and we have enough tests on other native interfaces.
|
||||
|
||||
from dummy import Dummy
|
||||
|
||||
d = Dummy()
|
||||
print(d)
|
||||
|
||||
assert(d.val == 0) ## @getter
|
||||
d.val = 3.14 ## @setter
|
||||
assert(d.val == 3.14)
|
||||
|
||||
assert(d == 3.14) ## == overload
|
||||
assert(d > 3) ## > overload
|
||||
assert(d >= 3) ## > overload
|
||||
assert(d >= 3.14) ## > and == overload
|
||||
|
||||
assert(d.a_method(12, 34) == 408) ## method
|
||||
|
||||
# If we got here, that means all test were passed.
|
||||
print('All TESTS PASSED')
|
@ -28,6 +28,7 @@ TEST_SUITE = {
|
||||
),
|
||||
|
||||
"Modules Test" : (
|
||||
"modules/dummy.pk",
|
||||
"modules/math.pk",
|
||||
),
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user