diff --git a/chunk.h b/chunk.h index 4910aa7..f18c443 100644 --- a/chunk.h +++ b/chunk.h @@ -50,6 +50,12 @@ typedef enum { OP_SET_UPVALUE, OP_SET_UPVALUE_LONG, OP_CLOSE_UPVALUE, + OP_CLASS, + OP_CLASS_LONG, + OP_SET_PROPERTY, + OP_SET_PROPERTY_LONG, + OP_GET_PROPERTY, + OP_GET_PROPERTY_LONG, } KrkOpCode; /** diff --git a/compiler.c b/compiler.c index 0fcfbb3..6c49bc6 100644 --- a/compiler.c +++ b/compiler.c @@ -108,6 +108,8 @@ static void statement(); static void declaration(); static void or_(int canAssign); static void and_(int canAssign); +static void classDeclaration(); +static void declareVariable(); static void errorAt(KrkToken * token, const char * message) { if (parser.panicMode) return; @@ -260,6 +262,17 @@ static void call(int canAssign) { emitBytes(OP_CALL, argCount); } +static void dot(int canAssign) { + consume(TOKEN_IDENTIFIER, "Expected propert name"); + size_t ind = identifierConstant(&parser.previous); + if (canAssign && match(TOKEN_EQUAL)) { + expression(); + EMIT_CONSTANT_OP(OP_SET_PROPERTY, ind); + } else { + EMIT_CONSTANT_OP(OP_GET_PROPERTY, ind); + } +} + static void literal(int canAssign) { switch (parser.previous.type) { case TOKEN_FALSE: emitByte(OP_FALSE); break; @@ -317,6 +330,8 @@ static void declaration() { defDeclaration(); } else if (match(TOKEN_LET)) { varDeclaration(); + } else if (check(TOKEN_CLASS)) { + classDeclaration(); } else if (check(TOKEN_EOL)) { return; } else { @@ -403,6 +418,21 @@ static void function(FunctionType type, int blockWidth) { } } +static void classDeclaration() { + int blockWidth = (parser.previous.type == TOKEN_INDENTATION) ? parser.previous.length : 0; + advance(); /* Collect the `class` */ + + consume(TOKEN_IDENTIFIER, "Expected class name."); + size_t constInd = identifierConstant(&parser.previous); + declareVariable(); + + EMIT_CONSTANT_OP(OP_CLASS, constInd); + defineVariable(constInd); + + consume(TOKEN_COLON, "Expected colon after class"); + /* TODO block semantics */ +} + static void markInitialized() { if (current->scopeDepth == 0) return; current->locals[current->localCount - 1].depth = current->scopeDepth; @@ -682,7 +712,7 @@ ParseRule rules[] = { [TOKEN_RIGHT_SQUARE] = {NULL, NULL, PREC_NONE}, [TOKEN_COLON] = {NULL, NULL, PREC_NONE}, [TOKEN_COMMA] = {NULL, NULL, PREC_NONE}, - [TOKEN_DOT] = {NULL, NULL, PREC_NONE}, + [TOKEN_DOT] = {NULL, dot, PREC_CALL}, [TOKEN_MINUS] = {unary, binary, PREC_TERM}, [TOKEN_PLUS] = {NULL, binary, PREC_TERM}, [TOKEN_SEMICOLON] = {NULL, NULL, PREC_NONE}, diff --git a/debug.c b/debug.c index 8c3f169..305eb76 100644 --- a/debug.c +++ b/debug.c @@ -75,6 +75,9 @@ size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) { CONSTANT(OP_CONSTANT,(void)0) CONSTANT(OP_GET_GLOBAL,(void)0) CONSTANT(OP_SET_GLOBAL,(void)0) + CONSTANT(OP_CLASS,(void)0) + CONSTANT(OP_GET_PROPERTY, (void)0) + CONSTANT(OP_SET_PROPERTY, (void)0) CONSTANT(OP_CLOSURE, CLOSURE_MORE) OPERANDL(OP_SET_LOCAL) OPERANDL(OP_GET_LOCAL) diff --git a/memory.c b/memory.c index 8839ced..619c3c9 100644 --- a/memory.c +++ b/memory.c @@ -2,6 +2,7 @@ #include "memory.h" #include "object.h" #include "compiler.h" +#include "table.h" void * krk_reallocate(void * ptr, size_t old, size_t new) { vm.bytesAllocated += new - old; @@ -49,6 +50,15 @@ static void freeObject(KrkObj * object) { FREE(KrkUpvalue, object); break; } + case OBJ_CLASS: { + FREE(KrkClass, object); + break; + } + case OBJ_INSTANCE: { + krk_freeTable(&((KrkInstance*)object)->fields); + FREE(KrkInstance, object); + break; + } } } @@ -105,6 +115,16 @@ static void blackenObject(KrkObj * object) { case OBJ_UPVALUE: krk_markValue(((KrkUpvalue*)object)->closed); break; + case OBJ_CLASS: { + KrkClass * _class = (KrkClass *)object; + krk_markObject((KrkObj*)_class->name); + break; + } + case OBJ_INSTANCE: { + krk_markObject((KrkObj*)((KrkInstance*)object)->_class); + krk_markTable(&((KrkInstance*)object)->fields); + break; + } case OBJ_NATIVE: case OBJ_STRING: break; diff --git a/object.c b/object.c index 0ff479f..a08618a 100644 --- a/object.c +++ b/object.c @@ -79,6 +79,12 @@ void krk_printObject(FILE * f, KrkValue value) { case OBJ_UPVALUE: fprintf(f, ""); break; + case OBJ_CLASS: + fprintf(f, "", AS_CLASS(value)->name->chars); + break; + case OBJ_INSTANCE: + fprintf(f, "", AS_INSTANCE(value)->_class->name->chars); + break; } } @@ -116,3 +122,16 @@ KrkUpvalue * newUpvalue(KrkValue * slot) { upvalue->closed = NONE_VAL(); return upvalue; } + +KrkClass * newClass(KrkString * name) { + KrkClass * _class = ALLOCATE_OBJECT(KrkClass, OBJ_CLASS); + _class->name = name; + return _class; +} + +KrkInstance * newInstance(KrkClass * _class) { + KrkInstance * instance = ALLOCATE_OBJECT(KrkInstance, OBJ_INSTANCE); + instance->_class = _class; + krk_initTable(&instance->fields); + return instance; +} diff --git a/object.h b/object.h index ee01c02..8da6557 100644 --- a/object.h +++ b/object.h @@ -5,6 +5,7 @@ #include "kuroko.h" #include "value.h" #include "chunk.h" +#include "table.h" #define OBJECT_TYPE(value) (AS_OBJECT(value)->type) #define IS_STRING(value) isObjType(value, OBJ_STRING) @@ -16,6 +17,10 @@ #define AS_NATIVE(value) (((KrkNative *)AS_OBJECT(value))->function) #define IS_CLOSURE(value) isObjType(value, OBJ_CLOSURE) #define AS_CLOSURE(value) ((KrkClosure *)AS_OBJECT(value)) +#define IS_CLASS(value) isObjType(value, OBJ_CLASS) +#define AS_CLASS(value) ((KrkClass *)AS_OBJECT(value)) +#define IS_INSTANCE(value) isObjType(value, OBJ_INSTANCE) +#define AS_INSTANCE(value) ((KrkInstance *)AS_OBJECT(value)) typedef enum { OBJ_FUNCTION, @@ -23,6 +28,8 @@ typedef enum { OBJ_CLOSURE, OBJ_STRING, OBJ_UPVALUE, + OBJ_CLASS, + OBJ_INSTANCE, } ObjType; struct Obj { @@ -60,6 +67,16 @@ typedef struct { size_t upvalueCount; } KrkClosure; +typedef struct { + KrkObj obj; + KrkString * name; +} KrkClass; + +typedef struct { + KrkObj obj; + KrkClass * _class; + KrkTable fields; +} KrkInstance; typedef KrkValue (*NativeFn)(int argCount, KrkValue* args); typedef struct { @@ -74,10 +91,9 @@ static inline int isObjType(KrkValue value, ObjType type) { extern KrkString * takeString(char * chars, size_t length); extern KrkString * copyString(const char * chars, size_t length); extern void krk_printObject(FILE * f, KrkValue value); - extern KrkFunction * newFunction(); extern KrkNative * newNative(NativeFn function); - extern KrkClosure * newClosure(KrkFunction * function); - extern KrkUpvalue * newUpvalue(KrkValue * slot); +extern KrkClass * newClass(KrkString * name); +extern KrkInstance * newInstance(KrkClass * _class); diff --git a/table.h b/table.h index 30150f6..7d8fc88 100644 --- a/table.h +++ b/table.h @@ -10,7 +10,6 @@ #include #include "kuroko.h" #include "value.h" -#include "object.h" typedef struct { KrkValue key; diff --git a/test.krk b/test.krk index f89b7a9..7531dc5 100644 --- a/test.krk +++ b/test.krk @@ -59,12 +59,16 @@ print "The function call returned: " + result # figured something with arguments would be more useful? The purpose of this # language is to be used for writing syntax highlighters, configs, and also # plugins for bim, so native bindings are going to be very important. -result = sleep(0.5) +result = sleep(0.1) print "Call to sleep returned: " + result function("something else") +# This is some stuff to test closures +class Funcs: + +let funcs = Funcs() if True: let a = 1 def f(): @@ -75,6 +79,13 @@ if True: let c = 3 def h(): print c + funcs.f = f + funcs.g = g + funcs.h = h + +funcs.f() +funcs.g() +funcs.h() def outer(): let x = "outside" @@ -85,4 +96,16 @@ def outer(): print "Function is defined, creating it..." let closure = outer() print "And executing the result..." +# This should correctly print "outside" closure() + +# This is surprisingly similar to Python already... +print "Let's do some classes." +class Test: # This is a test class + +print Test +let test = Test() +print test +test.foo = "bar" +print test.foo + diff --git a/vm.c b/vm.c index 4ed9326..317e5c1 100644 --- a/vm.c +++ b/vm.c @@ -54,7 +54,8 @@ void krk_push(KrkValue value) { KrkValue krk_pop() { vm.stackTop--; if (vm.stackTop < vm.stack) { - fprintf(stderr, "XXX: Stack overflow?"); + fprintf(stderr, "stack overflow - too many pops\n"); + return NONE_VAL(); } return *vm.stackTop; } @@ -115,6 +116,11 @@ static int callValue(KrkValue callee, int argCount) { krk_push(result); return 1; } + case OBJ_CLASS: { + KrkClass * _class = AS_CLASS(callee); + vm.stackTop[-argCount - 1] = OBJECT_VAL(newInstance(_class)); + return 1; + } default: break; } @@ -323,10 +329,8 @@ static KrkValue run() { closeUpvalues(frame->slots); vm.frameCount--; if (vm.frameCount == 0) { - krk_pop(); - return INTEGER_VAL(0); + return result; } - vm.stackTop = frame->slots; krk_push(result); frame = &vm.frames[vm.frameCount - 1]; @@ -466,6 +470,43 @@ static KrkValue run() { closeUpvalues(vm.stackTop - 1); krk_pop(); break; + case OP_CLASS_LONG: + case OP_CLASS: { + KrkString * name = READ_STRING((opcode == OP_CLASS ? 1 : 3)); + krk_push(OBJECT_VAL(newClass(name))); + break; + } + case OP_GET_PROPERTY_LONG: + case OP_GET_PROPERTY: { + if (!IS_INSTANCE(krk_peek(0))) { + runtimeError("Don't know how to retreive properties for %s yet", typeName(krk_peek(0))); + return NONE_VAL(); + } + KrkInstance * instance = AS_INSTANCE(krk_peek(0)); + KrkString * name = READ_STRING((opcode == OP_GET_PROPERTY ? 1 : 3)); + KrkValue value; + if (krk_tableGet(&instance->fields, OBJECT_VAL(name), &value)) { + krk_pop(); + krk_push(value); + break; + } + runtimeError("Undefined property '%s'.", name->chars); + return NONE_VAL(); + } + case OP_SET_PROPERTY_LONG: + case OP_SET_PROPERTY: { + if (!IS_INSTANCE(krk_peek(1))) { + runtimeError("Don't know how to set properties for %s yet", typeName(krk_peek(1))); + return NONE_VAL(); + } + KrkInstance * instance = AS_INSTANCE(krk_peek(1)); + KrkString * name = READ_STRING((opcode == OP_SET_PROPERTY ? 1 : 3)); + krk_tableSet(&instance->fields, OBJECT_VAL(name), krk_peek(0)); + KrkValue value = krk_pop(); + krk_pop(); /* instance */ + krk_push(value); /* Moves value in */ + break; + } } }