diff --git a/cli/all.c b/cli/all.c index 294fd44..99e07e2 100644 --- a/cli/all.c +++ b/cli/all.c @@ -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" diff --git a/cli/modules/modules.h b/cli/modules/modules.h index 0088f4a..1cb1ca8 100644 --- a/cli/modules/modules.h +++ b/cli/modules/modules.h @@ -14,6 +14,7 @@ #include #include +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); \ diff --git a/cli/modules/std_dummy.c b/cli/modules/std_dummy.c new file mode 100644 index 0000000..bee72e9 --- /dev/null +++ b/cli/modules/std_dummy.c @@ -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); +} diff --git a/docs/TODO.txt b/docs/TODO.txt index 451c8c0..4c634ad 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -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. diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 3834a7c..65b3a6f 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -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); diff --git a/src/pk_core.c b/src/pk_core.c index 2c2e8f6..292d5f5 100644 --- a/src/pk_core.c +++ b/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; diff --git a/src/pk_core.h b/src/pk_core.h index e6149e9..78c8d6a 100644 --- a/src/pk_core.h +++ b/src/pk_core.h @@ -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. diff --git a/src/pk_debug.c b/src/pk_debug.c index d1e4bc1..e25e9fb 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -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: diff --git a/src/pk_internal.h b/src/pk_internal.h index db1f9e0..56d2cf6 100644 --- a/src/pk_internal.h +++ b/src/pk_internal.h @@ -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 */ /*****************************************************************************/ diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index 0d7f37f..a84c3a2 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -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) diff --git a/src/pk_public.c b/src/pk_public.c index 8692992..db57551 100644 --- a/src/pk_public.c +++ b/src/pk_public.c @@ -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) { diff --git a/src/pk_value.c b/src/pk_value.c index e00ca38..9089729 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -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; diff --git a/src/pk_value.h b/src/pk_value.h index 507fee5..ae6ec2f 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -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); diff --git a/src/pk_vm.c b/src/pk_vm.c index eab6b42..b0caea9 100644 --- a/src/pk_vm.c +++ b/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(); } diff --git a/src/pk_vm.h b/src/pk_vm.h index 960f6b0..d1e1f41 100644 --- a/src/pk_vm.h +++ b/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 diff --git a/tests/lang/basics.pk b/tests/lang/basics.pk index f415e0f..2f316b8 100644 --- a/tests/lang/basics.pk +++ b/tests/lang/basics.pk @@ -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) diff --git a/tests/lang/class.pk b/tests/lang/class.pk index 283d8f1..fecfa6c 100644 --- a/tests/lang/class.pk +++ b/tests/lang/class.pk @@ -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') - - diff --git a/tests/modules/dummy.pk b/tests/modules/dummy.pk new file mode 100644 index 0000000..98c8e43 --- /dev/null +++ b/tests/modules/dummy.pk @@ -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') diff --git a/tests/tests.py b/tests/tests.py index 637ed1e..1aa5160 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -28,6 +28,7 @@ TEST_SUITE = { ), "Modules Test" : ( + "modules/dummy.pk", "modules/math.pk", ),