From acf38a31ca26d78f7337a65377a94e27bea84f59 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Tue, 26 Apr 2022 21:57:35 +0530 Subject: [PATCH] inheritance and 'is' test implemented --- src/pk_compiler.c | 19 +++++++++++++--- src/pk_core.c | 17 ++++++++++++++ src/pk_core.h | 6 ++++- src/pk_debug.c | 3 ++- src/pk_opcodes.h | 7 ++++-- src/pk_vm.c | 37 +++++++++++++++++++++++++++++-- tests/lang/class.pk | 54 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 134 insertions(+), 9 deletions(-) diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 1660132..3834a7c 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -120,6 +120,7 @@ typedef enum { TK_NULL, // null TK_IN, // in + TK_IS, // is TK_AND, // and TK_OR, // or TK_NOT, // not / ! @@ -183,6 +184,7 @@ static _Keyword _keywords[] = { { "end", 3, TK_END }, { "null", 4, TK_NULL }, { "in", 2, TK_IN }, + { "is", 2, TK_IS }, { "and", 3, TK_AND }, { "or", 2, TK_OR }, { "not", 3, TK_NOT }, @@ -1592,6 +1594,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence /* TK_END */ NO_RULE, /* TK_NULL */ { exprValue, NULL, NO_INFIX }, /* TK_IN */ { NULL, exprBinaryOp, PREC_TEST }, + /* TK_IS */ { NULL, exprBinaryOp, PREC_TEST }, /* TK_AND */ { NULL, exprAnd, PREC_LOGICAL_AND }, /* TK_OR */ { NULL, exprOr, PREC_LOGICAL_OR }, /* TK_NOT */ { exprUnaryOp, NULL, PREC_UNARY }, @@ -1936,6 +1939,7 @@ static void exprBinaryOp(Compiler* compiler) { 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; default: UNREACHABLE(); } @@ -2442,11 +2446,21 @@ static int compileClass(Compiler* compiler) { checkMaxConstantsReached(compiler, cls_index); - emitOpcode(compiler, OP_PUSH_CLASS); + if (match(compiler, TK_IS)) { + consume(compiler, TK_NAME, "Expected a class name to inherit."); + if (!compiler->parser.has_syntax_error) { + exprName(compiler); // Push the super class on the stack. + } + } else { + // Implicitly inherit from 'Object' class. + emitPushValue(compiler, NAME_BUILTIN_TY, (int)PK_OBJECT); + } + + emitOpcode(compiler, OP_CREATE_CLASS); emitShort(compiler, cls_index); skipNewLines(compiler); - while (!match(compiler, TK_END)) { + while (!compiler->parser.has_syntax_error && !match(compiler, TK_END)) { if (match(compiler, TK_EOF)) { syntaxError(compiler, compiler->parser.previous, @@ -2471,7 +2485,6 @@ static int compileClass(Compiler* compiler) { compiler->func->stack_size == 1, OOPS); skipNewLines(compiler); - if (compiler->parser.has_syntax_error) break; } int global_index = compilerAddVariable(compiler, name, name_len, name_line); diff --git a/src/pk_core.c b/src/pk_core.c index f3c2b80..0e7449d 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -1148,6 +1148,23 @@ bool varContains(PKVM* vm, Var elem, Var container) { return VAR_NULL; } +bool varIsType(PKVM* vm, Var inst, Var type) { + if (!IS_OBJ_TYPE(type, OBJ_CLASS)) { + VM_SET_ERROR(vm, newString(vm, "Right operand must be a class.")); + return VAR_NULL; + } + + Class* cls = (Class*)AS_OBJ(type); + Class* cls_inst = getClass(vm, inst); + + do { + if (cls_inst == cls) return true; + cls_inst = cls_inst->super_class; + } while (cls_inst != NULL); + + return false; +} + Var varGetAttrib(PKVM* vm, Var on, String* attrib) { #define ERR_NO_ATTRIB(vm, on, attrib) \ diff --git a/src/pk_core.h b/src/pk_core.h index 76db4df..e6149e9 100644 --- a/src/pk_core.h +++ b/src/pk_core.h @@ -62,9 +62,13 @@ 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. -// Returns [elem] in [container]. +// Returns [elem] in [container]. Sets an error if the [container] is not an +// iterable. bool varContains(PKVM* vm, Var elem, Var container); +// Returns [inst] is [type]. Sets an error if the [type] is not a class. +bool varIsType(PKVM* vm, Var inst, Var type); + // Returns the attribute named [attrib] on the variable [on]. Var varGetAttrib(PKVM* vm, Var on, String* attrib); diff --git a/src/pk_debug.c b/src/pk_debug.c index cf8f251..d1e4bc1 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -472,7 +472,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) { break; } - case OP_PUSH_CLASS: + case OP_CREATE_CLASS: { int index = READ_SHORT(); ASSERT_INDEX((uint32_t)index, func->owner->constants.count); @@ -609,6 +609,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) { case OP_GTEQ: case OP_RANGE: case OP_IN: + case OP_IS: case OP_REPL_PRINT: case OP_END: NO_ARGS(); diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index d033fb2..0d7f37f 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -111,9 +111,11 @@ OPCODE(STORE_UPVALUE, 1, 0) // params: 2 byte index. OPCODE(PUSH_CLOSURE, 2, 1) -// Push a class at the constant pool with the index of the two bytes argument. +// Pop the stack top, which expected to be the super class of the next class +// to be created and push that class from the constant pool with the index of +// the two bytes argument. // params: 2 byte index. -OPCODE(PUSH_CLASS, 2, 1) +OPCODE(CREATE_CLASS, 2, 0) // At the stack top, a closure and a class should be there. Add the method to // the class and pop it. @@ -248,6 +250,7 @@ OPCODE(GTEQ, 0, -1) OPCODE(RANGE, 0, -1) //< Pop 2 integer make range push. OPCODE(IN, 0, -1) +OPCODE(IS, 0, -1) // Print the repr string of the value at the stack top, used in REPL mode. // This will not pop the value. diff --git a/src/pk_vm.c b/src/pk_vm.c index 4bcc36c..fa50c5f 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -799,12 +799,30 @@ L_vm_main_loop: DISPATCH(); } - OPCODE(PUSH_CLASS): + OPCODE(CREATE_CLASS): { + Var cls = POP(); + if (!IS_OBJ_TYPE(cls, OBJ_CLASS)) { + RUNTIME_ERROR(newString(vm, "Cannot inherit a non class object.")); + } + + Class* base = (Class*)AS_OBJ(cls); + + // All Builtin type class except for Object are "final" ie. cannot be + // inherited from. + if (base->class_of != PK_INSTANCE && base->class_of != PK_OBJECT) { + RUNTIME_ERROR(stringFormat(vm, "$ type cannot be inherited.", + getPkVarTypeName(base->class_of))); + } + uint16_t index = READ_SHORT(); ASSERT_INDEX(index, module->constants.count); ASSERT(IS_OBJ_TYPE(module->constants.data[index], OBJ_CLASS), OOPS); - PUSH(module->constants.data[index]); + + Class* drived = (Class*)AS_OBJ(module->constants.data[index]); + drived->super_class = base; + + PUSH(VAR_OBJ(drived)); DISPATCH(); } @@ -916,6 +934,10 @@ L_do_call: CHECK_ERROR(); closure = (const Closure*)(cls)->ctor; + while (closure == NULL && cls != NULL) { + cls = cls->super_class; + closure = cls->ctor; + } // No constructor is defined on the class. Just return self. if (closure == NULL) { @@ -1518,6 +1540,17 @@ L_do_call: DISPATCH(); } + OPCODE(IS): + { + // Don't pop yet, we need the reference for gc. + Var type = PEEK(-1), inst = PEEK(-2); + bool is = varIsType(vm, inst, type); + DROP(); DROP(); // container, elem + PUSH(VAR_BOOL(is)); + CHECK_ERROR(); + DISPATCH(); + } + OPCODE(REPL_PRINT): { if (vm->config.stdout_write != NULL) { diff --git a/tests/lang/class.pk b/tests/lang/class.pk index 952b2ba..030aa6f 100644 --- a/tests/lang/class.pk +++ b/tests/lang/class.pk @@ -28,6 +28,60 @@ print("v2 = ${v2.to_string()}") v3 = v1.add(v2); assert(v3.x == 4 and v3.y == 6) print("v3 = ${v3.to_string()}") +############################################################################### +## INHERITANCE +############################################################################### + +class Shape + def display() + return "${self.name} shape" + end +end + +class Circle is Shape + def _init(r) + self.r = r + self.name = "circle" + end + def area() + return 3.14 * self.r * self.r + end +end + +class Rectangle is Shape + def _init(w, h) + self.w = w; self.h = h + self.name = "rectangle" + end + def area() + return self.w * self.h + end +end + +class Square is Rectangle + def _init(w) + ## TODO: Currently there is no way of calling super(w, h) + ## so we're setting our self here. + self.w = w; self.h = w + self.name = "square" + end +end + +c = Circle(1) +assert(c.display() == "circle shape") +assert(c is Circle) +assert(c is Shape) + +r = Rectangle(2, 3) +assert(r is Shape) +assert(r.area() == 6) + +s = Square(4) +assert(s is Square) +assert(s is Rectangle) +assert(s is Shape) +assert(s.area() == 16) + print('ALL TESTS PASSED')