From 7a343f2ecf37c0d491aecf9517919822878fa26d Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 24 Nov 2013 13:38:31 -0800 Subject: [PATCH] List literals. --- src/compiler.c | 26 ++++++++-- src/primitives.c | 10 ++++ src/value.h | 12 +++++ src/vm.c | 98 +++++++++++++++++++++++++++++++++---- src/vm.h | 20 ++++++-- test/class_inheritance.wren | 1 + test/list_count.wren | 7 +++ 7 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 test/list_count.wren diff --git a/src/compiler.c b/src/compiler.c index dc269ba0..67a0e937 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -783,6 +783,27 @@ static void grouping(Compiler* compiler, int allowAssignment) consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression."); } +static void list(Compiler* compiler, int allowAssignment) +{ + // Compile the list elements. + int numElements = 0; + if (peek(compiler) != TOKEN_RIGHT_BRACKET) + { + do + { + numElements++; + assignment(compiler); + } while (match(compiler, TOKEN_COMMA)); + } + + consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements."); + + // Create the list. + emit(compiler, CODE_LIST); + // TODO(bob): Handle lists >255 elements. + emit(compiler, numElements); +} + // Unary operators like `-foo`. static void unaryOp(Compiler* compiler, int allowAssignment) { @@ -1145,14 +1166,13 @@ void mixedSignature(Compiler* compiler, char* name, int* length) #define PREFIX(fn) { fn, NULL, NULL, PREC_NONE, NULL } #define INFIX(prec, fn) { NULL, fn, NULL, prec, NULL } #define INFIX_OPERATOR(prec, name) { NULL, infixOp, infixSignature, prec, name } -#define OPERATOR(prec, name) { unaryOp, infixOp, mixedSignature, prec, name } #define PREFIX_OPERATOR(name) { unaryOp, NULL, unarySignature, PREC_NONE, name } GrammarRule rules[] = { /* TOKEN_LEFT_PAREN */ PREFIX(grouping), /* TOKEN_RIGHT_PAREN */ UNUSED, - /* TOKEN_LEFT_BRACKET */ INFIX(PREC_CALL, subscript), + /* TOKEN_LEFT_BRACKET */ { list, subscript, NULL, PREC_CALL, NULL }, /* TOKEN_RIGHT_BRACKET */ UNUSED, /* TOKEN_LEFT_BRACE */ UNUSED, /* TOKEN_RIGHT_BRACE */ UNUSED, @@ -1163,7 +1183,7 @@ GrammarRule rules[] = /* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/ "), /* TOKEN_PERCENT */ INFIX_OPERATOR(PREC_TERM, "% "), /* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+ "), - /* TOKEN_MINUS */ OPERATOR(PREC_TERM, "- "), + /* TOKEN_MINUS */ { unaryOp, infixOp, mixedSignature, PREC_TERM, "- " }, /* TOKEN_PIPE */ UNUSED, /* TOKEN_PIPEPIPE */ INFIX(PREC_LOGIC, or), /* TOKEN_AMP */ UNUSED, diff --git a/src/primitives.c b/src/primitives.c index 4753ff8e..8408971f 100644 --- a/src/primitives.c +++ b/src/primitives.c @@ -85,6 +85,12 @@ DEF_PRIMITIVE(fn_bangeq) return BOOL_VAL(AS_FN(args[0]) != AS_FN(args[1])); } +DEF_PRIMITIVE(list_count) +{ + ObjList* list = AS_LIST(args[0]); + return NUM_VAL(list->count); +} + DEF_PRIMITIVE(num_abs) { return NUM_VAL(fabs(AS_NUM(args[0]))); @@ -277,6 +283,7 @@ static const char* CORE_LIB = "class Bool {}\n" "class Class {}\n" "class Function {}\n" +"class List {}\n" "class Num {}\n" "class Null {}\n" "class String {}\n" @@ -310,6 +317,9 @@ void loadCore(VM* vm) PRIMITIVE(vm->fnClass, "== ", fn_eqeq); PRIMITIVE(vm->fnClass, "!= ", fn_bangeq); + vm->listClass = AS_CLASS(findGlobal(vm, "List")); + PRIMITIVE(vm->listClass, "count", list_count); + vm->nullClass = AS_CLASS(findGlobal(vm, "Null")); vm->numClass = AS_CLASS(findGlobal(vm, "Num")); diff --git a/src/value.h b/src/value.h index 065b52cb..fd92158f 100644 --- a/src/value.h +++ b/src/value.h @@ -22,6 +22,7 @@ typedef enum { OBJ_CLASS, OBJ_FN, OBJ_INSTANCE, + OBJ_LIST, OBJ_STRING } ObjType; @@ -121,6 +122,14 @@ typedef struct Value fields[]; } ObjInstance; +typedef struct +{ + Obj obj; + int count; + // Pointer to a contiguous array of [length] elements. + Value* elements; +} ObjList; + typedef struct { Obj obj; @@ -137,6 +146,9 @@ typedef struct // Value -> ObjInstance*. #define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) +// Value -> ObjList*. +#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) + // Value -> double. #define AS_NUM(v) ((v).num) diff --git a/src/vm.c b/src/vm.c index 6f8d74bc..b1d75677 100644 --- a/src/vm.c +++ b/src/vm.c @@ -93,6 +93,15 @@ static void markInstance(ObjInstance* instance) } } +static void markList(ObjList* list) +{ + Value* elements = list->elements; + for (int i = 0; i < list->count; i++) + { + markValue(elements[i]); + } +} + static void markObj(Obj* obj) { #ifdef TRACE_MEMORY @@ -110,6 +119,7 @@ static void markObj(Obj* obj) case OBJ_CLASS: markClass((ObjClass*)obj); break; case OBJ_FN: markFn((ObjFn*)obj); break; case OBJ_INSTANCE: markInstance((ObjInstance*)obj); break; + case OBJ_LIST: markList((ObjList*)obj); break; case OBJ_STRING: // Just mark the string itself. obj->flags |= FLAG_MARKED; @@ -139,6 +149,10 @@ void freeObj(VM* vm, Obj* obj) switch (obj->type) { + case OBJ_CLASS: + size = sizeof(ObjClass); + break; + case OBJ_FN: { // TODO(bob): Don't hardcode array sizes. @@ -149,6 +163,25 @@ void freeObj(VM* vm, Obj* obj) break; } + case OBJ_INSTANCE: + { + size = sizeof(ObjInstance); + + // Include the size of the field array. + ObjInstance* instance = (ObjInstance*)obj; + size += sizeof(Value) * instance->classObj->numFields; + break; + } + + case OBJ_LIST: + { + size = sizeof(ObjList); + ObjList* list = (ObjList*)obj; + size += sizeof(Value) * list->count; + free(list->elements); + break; + } + case OBJ_STRING: { // TODO(bob): O(n) calculation here is lame! @@ -157,16 +190,6 @@ void freeObj(VM* vm, Obj* obj) free(string->value); break; } - - case OBJ_CLASS: - size = sizeof(ObjClass); - break; - - case OBJ_INSTANCE: - // Nothing to delete. - size = sizeof(Obj); - // TODO(bob): Include size of fields for OBJ_INSTANCE. - break; } vm->totalAllocated -= size; @@ -233,6 +256,8 @@ void collectGarbage(VM* vm) void* allocate(VM* vm, size_t size) { + ASSERT(size > 0, "Should not allocate 0 bytes."); + vm->totalAllocated += size; #ifdef DEBUG_GC_STRESS @@ -345,6 +370,23 @@ Value newInstance(VM* vm, ObjClass* classObj) return OBJ_VAL(instance); } +ObjList* newList(VM* vm, int numElements) +{ + // Allocate this before the list object in case it triggers a GC which would + // free the list. + Value* elements = NULL; + if (numElements > 0) + { + elements = allocate(vm, sizeof(Value) * numElements); + } + + ObjList* list = allocate(vm, sizeof(ObjList)); + initObj(vm, &list->obj, OBJ_LIST); + list->count = numElements; + list->elements = elements; + return list; +} + Value newString(VM* vm, const char* text, size_t length) { // Allocate before the string object in case this triggers a GC which would @@ -474,12 +516,20 @@ int dumpInstruction(VM* vm, ObjFn* fn, int i) break; case CODE_CLASS: + { + int numFields = bytecode[i++]; printf("CLASS\n"); + printf("%04d | num fields %d\n", i, numFields); break; + } case CODE_SUBCLASS: + { + int numFields = bytecode[i++]; printf("SUBCLASS\n"); + printf("%04d | num fields %d\n", i, numFields); break; + } case CODE_METHOD_INSTANCE: { @@ -511,6 +561,14 @@ int dumpInstruction(VM* vm, ObjFn* fn, int i) break; } + case CODE_LIST: + { + int count = bytecode[i++]; + printf("LIST\n"); + printf("%04d | count %d\n", i, count); + break; + } + case CODE_LOAD_LOCAL: { int local = bytecode[i++]; @@ -673,6 +731,7 @@ static ObjClass* getClass(VM* vm, Value value) case OBJ_CLASS: return AS_CLASS(value)->metaclass; case OBJ_FN: return vm->fnClass; case OBJ_INSTANCE: return AS_INSTANCE(value)->classObj; + case OBJ_LIST: return vm->listClass; case OBJ_STRING: return vm->stringClass; } } @@ -699,6 +758,7 @@ static ObjClass* getClass(VM* vm, Value value) { case OBJ_CLASS: return AS_CLASS(value)->metaclass; case OBJ_FN: return vm->fnClass; + case OBJ_LIST: return vm->listClass; case OBJ_STRING: return vm->stringClass; case OBJ_INSTANCE: return AS_INSTANCE(value)->classObj; } @@ -797,6 +857,22 @@ Value interpret(VM* vm, ObjFn* fn) break; } + case CODE_LIST: + { + int numElements = READ_ARG(); + ObjList* list = newList(vm, numElements); + for (int i = 0; i < numElements; i++) + { + list->elements[i] = fiber->stack[fiber->stackSize - numElements + i]; + } + + // Discard the elements. + fiber->stackSize -= numElements; + + PUSH(OBJ_VAL(list)); + break; + } + case CODE_METHOD_INSTANCE: case CODE_METHOD_STATIC: case CODE_METHOD_CTOR: @@ -1098,6 +1174,7 @@ void printValue(Value value) case OBJ_CLASS: printf("[class %p]", obj); break; case OBJ_FN: printf("[fn %p]", obj); break; case OBJ_INSTANCE: printf("[instance %p]", obj); break; + case OBJ_LIST: printf("[list %p]", obj); break; case OBJ_STRING: printf("%s", AS_CSTRING(value)); break; } } @@ -1124,6 +1201,7 @@ void printValue(Value value) case OBJ_CLASS: printf("[class %p]", value.obj); break; case OBJ_FN: printf("[fn %p]", value.obj); break; case OBJ_INSTANCE: printf("[instance %p]", value.obj); break; + case OBJ_LIST: printf("[list %p]", value.obj); break; case OBJ_STRING: printf("%s", AS_CSTRING(value)); break; } } diff --git a/src/vm.h b/src/vm.h index ee94f09b..aecd4769 100644 --- a/src/vm.h +++ b/src/vm.h @@ -42,11 +42,10 @@ typedef enum // the stack. CODE_METHOD_CTOR, - // Push a copy of the top of stack. - CODE_DUP, - - // Pop and discard the top of stack. - CODE_POP, + // Create a new list with [arg] elements. The top [arg] values on the stack + // are the elements in forward order. Removes the elements and then pushes + // the new list. + CODE_LIST, // Pushes the value in local slot [arg]. CODE_LOAD_LOCAL, @@ -66,6 +65,12 @@ typedef enum // Stores the top of stack in field slot [arg] in the current receiver. CODE_STORE_FIELD, + // Push a copy of the top of stack. + CODE_DUP, + + // Pop and discard the top of stack. + CODE_POP, + // Invoke the method with symbol [arg]. The number indicates the number of // arguments (not including the receiver). CODE_CALL_0, @@ -119,6 +124,7 @@ struct sVM ObjClass* boolClass; ObjClass* classClass; ObjClass* fnClass; + ObjClass* listClass; ObjClass* nullClass; ObjClass* numClass; ObjClass* objectClass; @@ -191,6 +197,10 @@ ObjClass* newClass(VM* vm, ObjClass* superclass, int numFields); // Creates a new instance of the given [classObj]. Value newInstance(VM* vm, ObjClass* classObj); +// Creates a new list with [numElements] elements (which are left +// uninitialized.) +ObjList* newList(VM* vm, int numElements); + // Creates a new string object and copies [text] into it. Value newString(VM* vm, const char* text, size_t length); diff --git a/test/class_inheritance.wren b/test/class_inheritance.wren index 409b76ea..dbb21a47 100644 --- a/test/class_inheritance.wren +++ b/test/class_inheritance.wren @@ -24,3 +24,4 @@ bar.method(1, 2, 3, 4) // expect: bar // TODO(bob): Private fields. // TODO(bob): Super (or inner) calls. // TODO(bob): Grammar for what expressions can follow "is". +// TODO(bob): Prevent extending built-in types. diff --git a/test/list_count.wren b/test/list_count.wren new file mode 100644 index 00000000..6b7d2654 --- /dev/null +++ b/test/list_count.wren @@ -0,0 +1,7 @@ +io.write([].count) // expect: 0 +io.write([1].count) // expect: 1 +io.write([1, 2, 3, 4].count) // expect: 4 + +// TODO(bob): Literal syntax, including newline handling. +// TODO(bob): Unterminated list literal. +// TODO(bob): Subscript operator.