From 915ecb4e586a51f1cde48856dc308e2f307c2c3f Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 10 Nov 2013 11:46:13 -0800 Subject: [PATCH] Static methods. --- src/compiler.c | 30 +++++++-- src/primitives.c | 2 +- src/vm.c | 138 +++++++++++++++++----------------------- src/vm.h | 4 ++ test/method_static.wren | 13 ++++ 5 files changed, 101 insertions(+), 86 deletions(-) create mode 100644 test/method_static.wren diff --git a/src/compiler.c b/src/compiler.c index 98093068..6482097f 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -40,8 +40,8 @@ typedef enum TOKEN_FN, TOKEN_IF, TOKEN_IS, - TOKEN_META, TOKEN_NULL, + TOKEN_STATIC, TOKEN_THIS, TOKEN_TRUE, TOKEN_VAR, @@ -263,8 +263,8 @@ static void readName(Parser* parser) if (isKeyword(parser, "fn")) type = TOKEN_FN; if (isKeyword(parser, "if")) type = TOKEN_IF; if (isKeyword(parser, "is")) type = TOKEN_IS; - if (isKeyword(parser, "meta")) type = TOKEN_META; if (isKeyword(parser, "null")) type = TOKEN_NULL; + if (isKeyword(parser, "static")) type = TOKEN_STATIC; if (isKeyword(parser, "this")) type = TOKEN_THIS; if (isKeyword(parser, "true")) type = TOKEN_TRUE; if (isKeyword(parser, "var")) type = TOKEN_VAR; @@ -454,7 +454,7 @@ static void nextToken(Parser* parser) case TOKEN_ELSE: case TOKEN_IF: case TOKEN_IS: - case TOKEN_META: + case TOKEN_STATIC: case TOKEN_VAR: parser->skipNewlines = 1; @@ -913,8 +913,8 @@ ParseRule rules[] = /* TOKEN_FN */ PREFIX(function), /* TOKEN_IF */ UNUSED, /* TOKEN_IS */ INFIX(PREC_IS, is), - /* TOKEN_META */ UNUSED, /* TOKEN_NULL */ PREFIX(null), + /* TOKEN_STATIC */ UNUSED, /* TOKEN_THIS */ PREFIX(this_), /* TOKEN_TRUE */ PREFIX(boolean), /* TOKEN_VAR */ UNUSED, @@ -1000,7 +1000,7 @@ void expression(Compiler* compiler) } // Compiles a method definition inside a class body. -void method(Compiler* compiler) +void method(Compiler* compiler, int isStatic) { char name[MAX_NAME]; int length = 0; @@ -1082,10 +1082,20 @@ void method(Compiler* compiler) // Add the block to the constant table. int constant = addConstant(compiler, (Value)methodCompiler.fn); + if (isStatic) + { + emit(compiler, CODE_METACLASS); + } + // Compile the code to define the method it. emit(compiler, CODE_METHOD); emit(compiler, symbol); emit(compiler, constant); + + if (isStatic) + { + emit(compiler, CODE_POP); + } } // Parses a "statement": any expression including expressions like variable @@ -1108,7 +1118,15 @@ void statement(Compiler* compiler) while (!match(compiler, TOKEN_RIGHT_BRACE)) { - method(compiler); + if (match(compiler, TOKEN_STATIC)) + { + method(compiler, 1); + } + else + { + method(compiler, 0); + } + consume(compiler, TOKEN_LINE); } diff --git a/src/primitives.c b/src/primitives.c index cc5e1e87..cfc98457 100644 --- a/src/primitives.c +++ b/src/primitives.c @@ -194,7 +194,7 @@ DEF_PRIMITIVE(string_plus) size_t leftLength = strlen(left); size_t rightLength = strlen(right); - char* result = malloc(leftLength + rightLength); + char* result = malloc(leftLength + rightLength + 1); strcpy(result, left); strcpy(result + leftLength, right); diff --git a/src/vm.c b/src/vm.c index b8f023e0..13b6effc 100644 --- a/src/vm.c +++ b/src/vm.c @@ -7,12 +7,6 @@ static Value primitive_metaclass_new(VM* vm, Fiber* fiber, Value* args); -// Pushes [value] onto the top of the stack. -static void push(Fiber* fiber, Value value); - -// Removes and returns the top of the stack. -static Value pop(Fiber* fiber); - VM* newVM() { VM* vm = malloc(sizeof(VM)); @@ -31,19 +25,23 @@ void freeVM(VM* vm) free(vm); } +void initObj(Obj* obj, ObjType type) +{ + obj->type = type; + obj->flags = 0; +} + Value makeBool(int value) { Obj* obj = malloc(sizeof(Obj)); - obj->type = value ? OBJ_TRUE : OBJ_FALSE; - obj->flags = 0; + initObj(obj, value ? OBJ_TRUE : OBJ_FALSE); return obj; } ObjClass* makeSingleClass() { ObjClass* obj = malloc(sizeof(ObjClass)); - obj->obj.type = OBJ_CLASS; - obj->obj.flags = 0; + initObj(&obj->obj, OBJ_CLASS); for (int i = 0; i < MAX_SYMBOLS; i++) { @@ -67,16 +65,14 @@ ObjClass* makeClass() ObjFn* makeFunction() { ObjFn* fn = malloc(sizeof(ObjFn)); - fn->obj.type = OBJ_FN; - fn->obj.flags = 0; + initObj(&fn->obj, OBJ_FN); return fn; } ObjInstance* makeInstance(ObjClass* classObj) { ObjInstance* instance = malloc(sizeof(ObjInstance)); - instance->obj.type = OBJ_INSTANCE; - instance->obj.flags = 0; + initObj(&instance->obj, OBJ_INSTANCE); instance->classObj = classObj; return instance; @@ -85,16 +81,14 @@ ObjInstance* makeInstance(ObjClass* classObj) Value makeNull() { Obj* obj = malloc(sizeof(Obj)); - obj->type = OBJ_NULL; - obj->flags = 0; + initObj(obj, OBJ_NULL); return obj; } ObjNum* makeNum(double number) { ObjNum* num = malloc(sizeof(ObjNum)); - num->obj.type = OBJ_NUM; - num->obj.flags = 0; + initObj(&num->obj, OBJ_NUM); num->value = number; return num; } @@ -102,8 +96,7 @@ ObjNum* makeNum(double number) ObjString* makeString(const char* text) { ObjString* string = malloc(sizeof(ObjString)); - string->obj.type = OBJ_STRING; - string->obj.flags = 0; + initObj(&string->obj, OBJ_STRING); string->value = text; return string; } @@ -213,6 +206,10 @@ void dumpCode(VM* vm, ObjFn* fn) printf("CLASS\n"); break; + case CODE_METACLASS: + printf("METACLASS\n"); + break; + case CODE_METHOD: { int symbol = bytecode[i++]; @@ -339,12 +336,21 @@ static ObjClass* getClass(VM* vm, Value object) Value interpret(VM* vm, ObjFn* fn) { + // TODO(bob): Allocate fiber on heap. Fiber fiber; fiber.stackSize = 0; fiber.numFrames = 0; callFunction(&fiber, fn, 0); + // These macros are designed to only be invoked within this function. + + // TODO(bob): Check for stack overflow. + #define PUSH(value) (fiber.stack[fiber.stackSize++] = value) + #define POP() (fiber.stack[--fiber.stackSize]) + #define PEEK() (fiber.stack[fiber.stackSize - 1]) + #define READ_ARG() (frame->fn->bytecode[frame->ip++]) + for (;;) { CallFrame* frame = &fiber.frames[fiber.numFrames - 1]; @@ -352,24 +358,12 @@ Value interpret(VM* vm, ObjFn* fn) switch (frame->fn->bytecode[frame->ip++]) { case CODE_CONSTANT: - { - int constant = frame->fn->bytecode[frame->ip++]; - Value value = frame->fn->constants[constant]; - push(&fiber, value); - break; - } - - case CODE_NULL: - push(&fiber, makeNull()); + PUSH(frame->fn->constants[READ_ARG()]); break; - case CODE_FALSE: - push(&fiber, makeBool(0)); - break; - - case CODE_TRUE: - push(&fiber, makeBool(1)); - break; + case CODE_NULL: PUSH(makeNull()); break; + case CODE_FALSE: PUSH(makeBool(0)); break; + case CODE_TRUE: PUSH(makeBool(1)); break; case CODE_CLASS: { @@ -382,15 +376,22 @@ Value interpret(VM* vm, ObjFn* fn) classObj->metaclass->methods[newSymbol].primitive = primitive_metaclass_new; - push(&fiber, (Value)classObj); + PUSH((Value)classObj); + break; + } + + case CODE_METACLASS: + { + ObjClass* classObj = AS_CLASS(PEEK()); + PUSH((Value)classObj->metaclass); break; } case CODE_METHOD: { - int symbol = frame->fn->bytecode[frame->ip++]; - int constant = frame->fn->bytecode[frame->ip++]; - ObjClass* classObj = (ObjClass*)fiber.stack[fiber.stackSize - 1]; + int symbol = READ_ARG(); + int constant = READ_ARG(); + ObjClass* classObj = AS_CLASS(PEEK()); ObjFn* body = AS_FN(frame->fn->constants[constant]); classObj->methods[symbol].type = METHOD_BLOCK; @@ -400,39 +401,34 @@ Value interpret(VM* vm, ObjFn* fn) case CODE_LOAD_LOCAL: { - int local = frame->fn->bytecode[frame->ip++]; - push(&fiber, fiber.stack[frame->stackStart + local]); + int local = READ_ARG(); + PUSH(fiber.stack[frame->stackStart + local]); break; } case CODE_STORE_LOCAL: { - int local = frame->fn->bytecode[frame->ip++]; - fiber.stack[frame->stackStart + local] = fiber.stack[fiber.stackSize - 1]; + int local = READ_ARG(); + fiber.stack[frame->stackStart + local] = PEEK(); break; } case CODE_LOAD_GLOBAL: { - int global = frame->fn->bytecode[frame->ip++]; - push(&fiber, vm->globals[global]); + int global = READ_ARG(); + PUSH(vm->globals[global]); break; } case CODE_STORE_GLOBAL: { - int global = frame->fn->bytecode[frame->ip++]; - vm->globals[global] = fiber.stack[fiber.stackSize - 1]; + int global = READ_ARG(); + vm->globals[global] = PEEK(); break; } - case CODE_DUP: - push(&fiber, fiber.stack[fiber.stackSize - 1]); - break; - - case CODE_POP: - pop(&fiber); - break; + case CODE_DUP: PUSH(PEEK()); break; + case CODE_POP: POP(); break; case CODE_CALL_0: case CODE_CALL_1: @@ -448,7 +444,7 @@ Value interpret(VM* vm, ObjFn* fn) { // Add one for the implicit receiver argument. int numArgs = frame->fn->bytecode[frame->ip - 1] - CODE_CALL_0 + 1; - int symbol = frame->fn->bytecode[frame->ip++]; + int symbol = READ_ARG(); Value receiver = fiber.stack[fiber.stackSize - numArgs]; @@ -489,17 +485,12 @@ Value interpret(VM* vm, ObjFn* fn) break; } - case CODE_JUMP: - { - int offset = frame->fn->bytecode[frame->ip++]; - frame->ip += offset; - break; - } + case CODE_JUMP: frame->ip += READ_ARG(); break; case CODE_JUMP_IF: { - int offset = frame->fn->bytecode[frame->ip++]; - Value condition = pop(&fiber); + int offset = READ_ARG(); + Value condition = POP(); // False is the only falsey value. if (condition->type == OBJ_FALSE) @@ -511,18 +502,18 @@ Value interpret(VM* vm, ObjFn* fn) case CODE_IS: { - Value classObj = pop(&fiber); - Value obj = pop(&fiber); + Value classObj = POP(); + Value obj = POP(); // TODO(bob): What if classObj is not a class? ObjClass* actual = getClass(vm, obj); - push(&fiber, makeBool(actual == AS_CLASS(classObj))); + PUSH(makeBool(actual == AS_CLASS(classObj))); break; } case CODE_END: { - Value result = pop(&fiber); + Value result = POP(); fiber.numFrames--; // If we are returning from the top-level block, just return the value. @@ -596,14 +587,3 @@ Value primitive_metaclass_new(VM* vm, Fiber* fiber, Value* args) // TODO(bob): Invoke initializer method. return (Value)makeInstance(classObj); } - -void push(Fiber* fiber, Value value) -{ - // TODO(bob): Check for stack overflow. - fiber->stack[fiber->stackSize++] = value; -} - -Value pop(Fiber* fiber) -{ - return fiber->stack[--fiber->stackSize]; -} diff --git a/src/vm.h b/src/vm.h index d385af79..d5a8f37e 100644 --- a/src/vm.h +++ b/src/vm.h @@ -120,6 +120,10 @@ typedef enum // Define a new empty class and push it. CODE_CLASS, + // Push the metaclass of the class on the top of the stack. Does not discard + // the class. + CODE_METACLASS, + // Add a method for symbol [arg1] with body stored in constant [arg2] to the // class on the top of stack. Does not modify the stack. CODE_METHOD, diff --git a/test/method_static.wren b/test/method_static.wren new file mode 100644 index 00000000..53d537f0 --- /dev/null +++ b/test/method_static.wren @@ -0,0 +1,13 @@ +class Foo { + bar { "on instance" } + static bar { "on metaclass" } + + bar(arg) { "on instance " + arg } + static bar(arg) { "on metaclass " + arg } +} + +io.write("on metaclass " + "arg") // expect: on metaclass arg +io.write(Foo.new.bar) // expect: on instance +io.write(Foo.bar) // expect: on metaclass +io.write(Foo.new.bar("arg")) // expect: on instance arg +io.write(Foo.bar("arg")) // expect: on metaclass arg