From d1a772e820ac72aecc34e4d380e577ad4f42dc0c Mon Sep 17 00:00:00 2001 From: "Diego F. Goberna" Date: Tue, 14 Nov 2017 16:56:16 +0100 Subject: [PATCH 01/44] parameter missing in errorFn --- doc/site/embedding/configuring-the-vm.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/site/embedding/configuring-the-vm.markdown b/doc/site/embedding/configuring-the-vm.markdown index 3c2b9a12..e8f6de49 100644 --- a/doc/site/embedding/configuring-the-vm.markdown +++ b/doc/site/embedding/configuring-the-vm.markdown @@ -102,6 +102,7 @@ is: :::c void error( + WrenVM* vm, WrenErrorType type, const char* module, int line, From a2cf3c34ad3c2d8e7f242be0c87137d55e1dd4bf Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Thu, 11 Jan 2018 11:56:03 +0100 Subject: [PATCH 02/44] Fix getNumArguments and wrenBindMethodCode opcodes data. Some instructions data in these 2 functions are out of syncs, and can try to patch the code in bad manner. --- src/vm/wren_compiler.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 0c18f8b0..66af1432 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -2730,7 +2730,6 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants, case CODE_LOAD_FIELD: case CODE_STORE_FIELD: case CODE_CLASS: - case CODE_IMPORT_MODULE: return 1; case CODE_CONSTANT: @@ -2760,7 +2759,7 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants, case CODE_OR: case CODE_METHOD_INSTANCE: case CODE_METHOD_STATIC: - case CODE_IMPORT_VARIABLE: + case CODE_IMPORT_MODULE: return 2; case CODE_SUPER_0: @@ -2780,6 +2779,7 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants, case CODE_SUPER_14: case CODE_SUPER_15: case CODE_SUPER_16: + case CODE_IMPORT_VARIABLE: return 4; case CODE_CLOSURE: @@ -3495,7 +3495,7 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) int ip = 0; for (;;) { - Code instruction = (Code)fn->code.data[ip++]; + Code instruction = (Code)fn->code.data[ip]; switch (instruction) { case CODE_LOAD_FIELD: @@ -3505,7 +3505,7 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) // Shift this class's fields down past the inherited ones. We don't // check for overflow here because we'll see if the number of fields // overflows when the subclass is created. - fn->code.data[ip++] += classObj->superclass->numFields; + fn->code.data[ip + 1] += classObj->superclass->numFields; break; case CODE_SUPER_0: @@ -3526,11 +3526,8 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) case CODE_SUPER_15: case CODE_SUPER_16: { - // Skip over the symbol. - ip += 2; - // Fill in the constant slot with a reference to the superclass. - int constant = (fn->code.data[ip] << 8) | fn->code.data[ip + 1]; + int constant = (fn->code.data[ip + 3] << 8) | fn->code.data[ip + 4]; fn->constants.data[constant] = OBJ_VAL(classObj->superclass); break; } @@ -3538,10 +3535,8 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) case CODE_CLOSURE: { // Bind the nested closure too. - int constant = (fn->code.data[ip] << 8) | fn->code.data[ip + 1]; + int constant = (fn->code.data[ip + 1] << 8) | fn->code.data[ip + 2]; wrenBindMethodCode(classObj, AS_FN(fn->constants.data[constant])); - - ip += getNumArguments(fn->code.data, fn->constants.data, ip - 1); break; } @@ -3550,9 +3545,9 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) default: // Other instructions are unaffected, so just skip over them. - ip += getNumArguments(fn->code.data, fn->constants.data, ip - 1); break; } + ip += 1 + getNumArguments(fn->code.data, fn->constants.data, ip); } } From e493e6679b0465bd690e4478948093cb65b38c2d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 18 Jan 2018 00:46:58 -0500 Subject: [PATCH 03/44] Fix variable in assert Fixes #493 --- src/vm/wren_vm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 2b044332..1d69f3fb 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -1745,7 +1745,7 @@ void wrenGetVariable(WrenVM* vm, const char* module, const char* name, int slot) { ASSERT(module != NULL, "Module cannot be NULL."); - ASSERT(module != NULL, "Variable name cannot be NULL."); + ASSERT(name != NULL, "Variable name cannot be NULL."); validateApiSlot(vm, slot); Value moduleName = wrenStringFormat(vm, "$", module); From 89ae3f92ec46ca80fef4b1957782ab3893cdd4f9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 18 Jan 2018 00:48:02 -0500 Subject: [PATCH 04/44] Add @maxdeviant to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 23d317f3..41abe44e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,3 +20,4 @@ Damien Radtke Max Ferguson Sven Bergström Kyle Charters +Marshall Bowers From 92cb88366bf105de322be3bf7c7b2d4c269fbb63 Mon Sep 17 00:00:00 2001 From: snocl Date: Mon, 19 Mar 2018 21:39:35 +0100 Subject: [PATCH 05/44] Fix REPL being broken by latest commit. --- src/module/repl.wren | 15 ++++++--------- src/module/repl.wren.inc | 15 ++++++--------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/module/repl.wren b/src/module/repl.wren index 6a471458..6a77478e 100644 --- a/src/module/repl.wren +++ b/src/module/repl.wren @@ -11,11 +11,6 @@ class Repl { _history = [] _historyIndex = 0 - - // Whether or not we allow ASCI escape sequences for showing colors and - // moving the cursor. When this is false, the REPL has reduced - // functionality. - _allowAnsi = false } cursor { _cursor } @@ -172,15 +167,17 @@ class Repl { token.type == Token.varKeyword || token.type == Token.whileKeyword - var fiber + var closure if (isStatement) { - fiber = Meta.compile(input) + closure = Meta.compile(input) } else { - fiber = Meta.compileExpression(input) + closure = Meta.compileExpression(input) } // Stop if there was a compile error. - if (fiber == null) return + if (closure == null) return + + var fiber = Fiber.new(closure) var result = fiber.try() if (fiber.error != null) { diff --git a/src/module/repl.wren.inc b/src/module/repl.wren.inc index 196fbcf3..dcd923da 100644 --- a/src/module/repl.wren.inc +++ b/src/module/repl.wren.inc @@ -13,11 +13,6 @@ static const char* replModuleSource = "\n" " _history = []\n" " _historyIndex = 0\n" -"\n" -" // Whether or not we allow ASCI escape sequences for showing colors and\n" -" // moving the cursor. When this is false, the REPL has reduced\n" -" // functionality.\n" -" _allowAnsi = false\n" " }\n" "\n" " cursor { _cursor }\n" @@ -174,15 +169,17 @@ static const char* replModuleSource = " token.type == Token.varKeyword ||\n" " token.type == Token.whileKeyword\n" "\n" -" var fiber\n" +" var closure\n" " if (isStatement) {\n" -" fiber = Meta.compile(input)\n" +" closure = Meta.compile(input)\n" " } else {\n" -" fiber = Meta.compileExpression(input)\n" +" closure = Meta.compileExpression(input)\n" " }\n" "\n" " // Stop if there was a compile error.\n" -" if (fiber == null) return\n" +" if (closure == null) return\n" +"\n" +" var fiber = Fiber.new(closure)\n" "\n" " var result = fiber.try()\n" " if (fiber.error != null) {\n" From bcc31e63a22edaf3284d9aa6572e1d68aed26fa4 Mon Sep 17 00:00:00 2001 From: Krzysztof Kowalczyk Date: Tue, 20 Mar 2018 17:13:28 -0700 Subject: [PATCH 06/44] add() method is bound as static but bindForeignMethod responds to non-static --- doc/site/embedding/calling-c-from-wren.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/site/embedding/calling-c-from-wren.markdown b/doc/site/embedding/calling-c-from-wren.markdown index ac6ab861..bc3df107 100644 --- a/doc/site/embedding/calling-c-from-wren.markdown +++ b/doc/site/embedding/calling-c-from-wren.markdown @@ -76,7 +76,7 @@ Something like: { if (strcmp(className, "Math") == 0) { - if (!isStatic && strcmp(signature, "add(_,_)") == 0) + if (isStatic && strcmp(signature, "add(_,_)") == 0) { return mathAdd; // C function for Math.add(_,_). } From 3e9bf0277b70e19bf500566d7e18ee4cd5639116 Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Wed, 28 Mar 2018 17:51:34 +0200 Subject: [PATCH 07/44] Move Obj* type definition to wren_common.h. Uniformize structure naming, and should help file includes. --- src/vm/wren_common.h | 13 +++++++++++ src/vm/wren_value.h | 53 ++++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/vm/wren_common.h b/src/vm/wren_common.h index c3b5682c..f22caee4 100644 --- a/src/vm/wren_common.h +++ b/src/vm/wren_common.h @@ -199,4 +199,17 @@ #endif +typedef struct sObjClass ObjClass; +typedef struct sObjClosure ObjClosure; +typedef struct sObjFiber ObjFiber; +typedef struct sObjForeign ObjForeign; +typedef struct sObjFn ObjFn; +typedef struct sObjInstance ObjInstance; +typedef struct sObjList ObjList; +typedef struct sObjMap ObjMap; +typedef struct sObjModule ObjModule; +typedef struct sObjRange ObjRange; +typedef struct sObjString ObjString; +typedef struct sObjUpvalue ObjUpvalue; + #endif diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index ffb79f08..c1245818 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -99,10 +99,9 @@ typedef enum { OBJ_UPVALUE } ObjType; -typedef struct sObjClass ObjClass; - // Base struct for all heap-allocated objects. -typedef struct sObj +typedef struct sObj Obj; +struct sObj { ObjType type; bool isDark; @@ -112,7 +111,7 @@ typedef struct sObj // The next object in the linked list of all currently allocated objects. struct sObj* next; -} Obj; +}; #if WREN_NAN_TAGGING @@ -145,7 +144,7 @@ typedef struct DECLARE_BUFFER(Value, Value); // A heap-allocated string object. -typedef struct +struct sObjString { Obj obj; @@ -157,7 +156,7 @@ typedef struct // Inline array of the string's bytes followed by a null terminator. char value[FLEXIBLE_ARRAY]; -} ObjString; +}; // The dynamically allocated data structure for a variable that has been used // by a closure. Whenever a function accesses a variable declared in an @@ -171,7 +170,7 @@ typedef struct // be closed. When that happens, the value gets copied off the stack into the // upvalue itself. That way, it can have a longer lifetime than the stack // variable. -typedef struct sUpvalue +struct sObjUpvalue { // The object header. Note that upvalues have this because they are garbage // collected, but they are not first class Wren objects. @@ -187,8 +186,8 @@ typedef struct sUpvalue // Open upvalues are stored in a linked list by the fiber. This points to the // next upvalue in that list. - struct sUpvalue* next; -} ObjUpvalue; + struct sObjUpvalue* next; +}; // The type of a primitive function. // @@ -217,7 +216,7 @@ typedef struct // // While this is an Obj and is managed by the GC, it never appears as a // first-class object in Wren. -typedef struct +struct sObjModule { Obj obj; @@ -230,7 +229,7 @@ typedef struct // The name of the module. ObjString* name; -} ObjModule; +}; // A function object. It wraps and owns the bytecode and other debug information // for a callable chunk of code. @@ -240,7 +239,7 @@ typedef struct // representation of a function. This isn't strictly necessary if they function // has no upvalues, but lets the rest of the VM assume all called objects will // be closures. -typedef struct +struct sObjFn { Obj obj; @@ -261,11 +260,11 @@ typedef struct // only be set for fns, and not ObjFns that represent methods or scripts. int arity; FnDebug* debug; -} ObjFn; +}; // An instance of a first-class function and the environment it has closed over. // Unlike [ObjFn], this has captured the upvalues that the function accesses. -typedef struct +struct sObjClosure { Obj obj; @@ -274,7 +273,7 @@ typedef struct // The upvalues this function has closed over. ObjUpvalue* upvalues[FLEXIBLE_ARRAY]; -} ObjClosure; +}; typedef struct { @@ -291,7 +290,7 @@ typedef struct Value* stackStart; } CallFrame; -typedef struct sObjFiber +struct sObjFiber { Obj obj; @@ -333,7 +332,7 @@ typedef struct sObjFiber // In that case, if this fiber fails with an error, the error will be given // to the caller. bool callerIsTrying; -} ObjFiber; +}; typedef enum { @@ -395,25 +394,25 @@ struct sObjClass ObjString* name; }; -typedef struct +struct sObjForeign { Obj obj; uint8_t data[FLEXIBLE_ARRAY]; -} ObjForeign; +}; -typedef struct +struct sObjInstance { Obj obj; Value fields[FLEXIBLE_ARRAY]; -} ObjInstance; +}; -typedef struct +struct sObjList { Obj obj; // The elements in the list. ValueBuffer elements; -} ObjList; +}; typedef struct { @@ -443,7 +442,7 @@ typedef struct // for a key, we will continue past tombstones, because the desired key may be // found after them if the key that was removed was part of a prior collision. // When the array gets resized, all tombstones are discarded. -typedef struct +struct sObjMap { Obj obj; @@ -455,9 +454,9 @@ typedef struct // Pointer to a contiguous array of [capacity] entries. MapEntry* entries; -} ObjMap; +}; -typedef struct +struct sObjRange { Obj obj; @@ -469,7 +468,7 @@ typedef struct // True if [to] is included in the range. bool isInclusive; -} ObjRange; +}; // An IEEE 754 double-precision float is a 64-bit value with bits laid out like: // From 3c39f7e0aff5213e5d8ff94559a5344e6ea2198b Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Wed, 28 Mar 2018 18:12:50 +0200 Subject: [PATCH 08/44] Make symboltable use ObjString. * Avoid to have 2 different string representation in the code. * Reduce memory creation in meta API, by reusing created strings. --- src/optional/wren_opt_meta.c | 4 +--- src/vm/wren_compiler.c | 4 ++-- src/vm/wren_debug.c | 12 ++++++------ src/vm/wren_utils.c | 37 ++++++++++++++++++++---------------- src/vm/wren_utils.h | 15 +++++---------- src/vm/wren_value.c | 3 ++- src/vm/wren_vm.c | 9 ++++++--- 7 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/optional/wren_opt_meta.c b/src/optional/wren_opt_meta.c index 504543be..ed05bf3c 100644 --- a/src/optional/wren_opt_meta.c +++ b/src/optional/wren_opt_meta.c @@ -52,9 +52,7 @@ void metaGetModuleVariables(WrenVM* vm) { for (int i = 0; i < names->elements.count; i++) { - String* name = &module->variableNames.data[i]; - names->elements.data[i] = wrenNewStringLength(vm, - name->buffer, name->length); + names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]); } } diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 66af1432..bc2f006f 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -3480,8 +3480,8 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, { // Synthesize a token for the original use site. parser.previous.type = TOKEN_NAME; - parser.previous.start = parser.module->variableNames.data[i].buffer; - parser.previous.length = parser.module->variableNames.data[i].length; + parser.previous.start = parser.module->variableNames.data[i]->value; + parser.previous.length = parser.module->variableNames.data[i]->length; parser.previous.line = (int)AS_NUM(parser.module->variables.data[i]); error(&compiler, "Variable is used but not defined."); } diff --git a/src/vm/wren_debug.c b/src/vm/wren_debug.c index f04d9fe6..1558a0bd 100644 --- a/src/vm/wren_debug.c +++ b/src/vm/wren_debug.c @@ -159,7 +159,7 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) { int slot = READ_SHORT(); printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot, - fn->module->variableNames.data[slot].buffer); + fn->module->variableNames.data[slot]->value); break; } @@ -167,7 +167,7 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) { int slot = READ_SHORT(); printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot, - fn->module->variableNames.data[slot].buffer); + fn->module->variableNames.data[slot]->value); break; } @@ -199,7 +199,7 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) int numArgs = bytecode[i - 1] - CODE_CALL_0; int symbol = READ_SHORT(); printf("CALL_%-11d %5d '%s'\n", numArgs, symbol, - vm->methodNames.data[symbol].buffer); + vm->methodNames.data[symbol]->value); break; } @@ -225,7 +225,7 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) int symbol = READ_SHORT(); int superclass = READ_SHORT(); printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol, - vm->methodNames.data[symbol].buffer, superclass); + vm->methodNames.data[symbol]->value, superclass); break; } @@ -301,7 +301,7 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) { int symbol = READ_SHORT(); printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol, - vm->methodNames.data[symbol].buffer); + vm->methodNames.data[symbol]->value); break; } @@ -309,7 +309,7 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) { int symbol = READ_SHORT(); printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol, - vm->methodNames.data[symbol].buffer); + vm->methodNames.data[symbol]->value); break; } diff --git a/src/vm/wren_utils.c b/src/vm/wren_utils.c index 1a3b8e61..617913cb 100644 --- a/src/vm/wren_utils.c +++ b/src/vm/wren_utils.c @@ -5,7 +5,7 @@ DEFINE_BUFFER(Byte, uint8_t); DEFINE_BUFFER(Int, int); -DEFINE_BUFFER(String, String); +DEFINE_BUFFER(String, ObjString*); void wrenSymbolTableInit(SymbolTable* symbols) { @@ -14,24 +14,18 @@ void wrenSymbolTableInit(SymbolTable* symbols) void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols) { - for (int i = 0; i < symbols->count; i++) - { - DEALLOCATE(vm, symbols->data[i].buffer); - } - wrenStringBufferClear(vm, symbols); } int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length) { - String symbol; - symbol.buffer = ALLOCATE_ARRAY(vm, char, length + 1); - memcpy(symbol.buffer, name, length); - symbol.buffer[length] = '\0'; - symbol.length = (int)length; - + ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length)); + + wrenPushRoot(vm, &symbol->obj); wrenStringBufferWrite(vm, symbols, symbol); + wrenPopRoot(vm); // symbol + return symbols->count - 1; } @@ -46,20 +40,31 @@ int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, return wrenSymbolTableAdd(vm, symbols, name, length); } -int wrenSymbolTableFind(const SymbolTable* symbols, - const char* name, size_t length) +int wrenSymbolTableFind(const SymbolTable* symbols, + const char* name, size_t length) { // See if the symbol is already defined. // TODO: O(n). Do something better. for (int i = 0; i < symbols->count; i++) { - if (symbols->data[i].length == length && - memcmp(symbols->data[i].buffer, name, length) == 0) return i; + if (symbols->data[i]->length == length && + memcmp(symbols->data[i]->value, name, length) == 0) return i; } return -1; } +void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable) +{ + for (int i = 0; i < symbolTable->count; i++) + { + wrenGrayObj(vm, &symbolTable->data[i]->obj); + } + + // Keep track of how much memory is still in use. + vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data); +} + int wrenUtf8EncodeNumBytes(int value) { ASSERT(value >= 0, "Cannot encode a negative value."); diff --git a/src/vm/wren_utils.h b/src/vm/wren_utils.h index 8d85c7cf..959f8526 100644 --- a/src/vm/wren_utils.h +++ b/src/vm/wren_utils.h @@ -6,13 +6,6 @@ // Reusable data structures and other utility functions. -// A simple structure to keep track of a string and its length (including the -// null-terminator). -typedef struct { - char* buffer; - uint32_t length; -} String; - // We need buffers of a few different types. To avoid lots of casting between // void* and back, we'll use the preprocessor as a poor man's generics and let // it generate a few type-specific ones. @@ -68,7 +61,7 @@ typedef struct { DECLARE_BUFFER(Byte, uint8_t); DECLARE_BUFFER(Int, int); -DECLARE_BUFFER(String, String); +DECLARE_BUFFER(String, ObjString*); // TODO: Change this to use a map. typedef StringBuffer SymbolTable; @@ -90,8 +83,10 @@ int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, const char* name, size_t length); // Looks up name in the symbol table. Returns its index if found or -1 if not. -int wrenSymbolTableFind(const SymbolTable* symbols, - const char* name, size_t length); +int wrenSymbolTableFind(const SymbolTable* symbols, + const char* name, size_t length); + +void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable); // Returns the number of bytes needed to encode [value] in UTF-8. // diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index e16f25f4..f4849015 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -1137,11 +1137,12 @@ static void blackenModule(WrenVM* vm, ObjModule* module) wrenGrayValue(vm, module->variables.data[i]); } + wrenBlackenSymbolTable(vm, &module->variableNames); + wrenGrayObj(vm, (Obj*)module->name); // Keep track of how much memory is still in use. vm->bytesAllocated += sizeof(ObjModule); - // TODO: Track memory for symbol table and buffer. } static void blackenRange(WrenVM* vm, ObjRange* range) diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 33c18e45..1954d737 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -150,6 +150,9 @@ void wrenCollectGarbage(WrenVM* vm) // Any object the compiler is using (if there is one). if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler); + // Method names. + wrenBlackenSymbolTable(vm, &vm->methodNames); + // Now that we have grayed the roots, do a depth-first search over all of the // reachable objects. wrenBlackenObjects(vm); @@ -420,7 +423,7 @@ static void runtimeError(WrenVM* vm) static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol) { vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.", - OBJ_VAL(classObj->name), vm->methodNames.data[symbol].buffer); + OBJ_VAL(classObj->name), vm->methodNames.data[symbol]->value); } // Checks that [value], which must be a closure, does not require more @@ -492,8 +495,8 @@ static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source, for (int i = 0; i < coreModule->variables.count; i++) { wrenDefineVariable(vm, module, - coreModule->variableNames.data[i].buffer, - coreModule->variableNames.data[i].length, + coreModule->variableNames.data[i]->value, + coreModule->variableNames.data[i]->length, coreModule->variables.data[i]); } } From e4901c51d8a33b71b3d1d27b472610ffa8905440 Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Thu, 29 Mar 2018 09:12:32 +0200 Subject: [PATCH 09/44] Add wrenStringEqualStrLength and wrenStringsEqual. --- src/vm/wren_utils.c | 3 +-- src/vm/wren_value.c | 6 +----- src/vm/wren_value.h | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/vm/wren_utils.c b/src/vm/wren_utils.c index 617913cb..e179cef5 100644 --- a/src/vm/wren_utils.c +++ b/src/vm/wren_utils.c @@ -47,8 +47,7 @@ int wrenSymbolTableFind(const SymbolTable* symbols, // TODO: O(n). Do something better. for (int i = 0; i < symbols->count; i++) { - if (symbols->data[i]->length == length && - memcmp(symbols->data[i]->value, name, length) == 0) return i; + if(wrenStringEqualStrLength(symbols->data[i], name, length)) return i; } return -1; diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index f4849015..fba93617 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -1295,11 +1295,7 @@ bool wrenValuesEqual(Value a, Value b) case OBJ_STRING: { - ObjString* aString = (ObjString*)aObj; - ObjString* bString = (ObjString*)bObj; - return aString->length == bString->length && - aString->hash == bString->hash && - memcmp(aString->value, bString->value, aString->length) == 0; + return wrenStringsEqual((ObjString*)aObj, (ObjString*)bObj); } default: diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index c1245818..1e37bb89 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -2,6 +2,7 @@ #define wren_value_h #include +#include #include "wren_common.h" #include "wren_utils.h" @@ -785,6 +786,22 @@ static inline bool wrenValuesSame(Value a, Value b) // other values are equal if they are identical objects. bool wrenValuesEqual(Value a, Value b); +// Returns true is [a] and [str] represent the same string. +static inline bool wrenStringEqualStrLength(const ObjString* a, + const char* str, size_t length) +{ + return a->length == length && + memcmp(a->value, str, length) == 0; +} + +// Returns true is [a] and [b] represent the same string. +static inline bool wrenStringsEqual(const ObjString* a, const ObjString* b) +{ + return a == b || + (a->hash == b->hash && + wrenStringEqualStrLength(a, b->value, b->length)); +} + // Returns true if [value] is a bool. Do not call this directly, instead use // [IS_BOOL]. static inline bool wrenIsBool(Value value) From 8858161484c1903bfd2de2f21a765738ff2d7fd3 Mon Sep 17 00:00:00 2001 From: jclc Date: Wed, 4 Apr 2018 05:11:59 +0300 Subject: [PATCH 10/44] skip UTF-8 BOM --- src/vm/wren_compiler.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 66af1432..c7f0de37 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -3416,20 +3416,27 @@ void definition(Compiler* compiler) ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, bool isExpression, bool printErrors) { + // Skip potential UTF-8 BOM + size_t sourceOffset = 0; + if (source[0] == (char) 0xef && source[1] == (char) 0xbb && source[2] == (char) 0xbf) + { + sourceOffset = 3; + } + Parser parser; parser.vm = vm; parser.module = module; - parser.source = source; + parser.source = source + sourceOffset; - parser.tokenStart = source; - parser.currentChar = source; + parser.tokenStart = source + sourceOffset; + parser.currentChar = source + sourceOffset; parser.currentLine = 1; parser.numParens = 0; // Zero-init the current token. This will get copied to previous when // advance() is called below. parser.current.type = TOKEN_ERROR; - parser.current.start = source; + parser.current.start = source + sourceOffset; parser.current.length = 0; parser.current.line = 0; parser.current.value = UNDEFINED_VAL; @@ -3439,6 +3446,7 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, parser.printErrors = printErrors; parser.hasError = false; + // Read the first token. nextToken(&parser); From fe2ca0e89a87a7e0ef1787cf5c963677924c2b75 Mon Sep 17 00:00:00 2001 From: jclc Date: Wed, 4 Apr 2018 05:14:05 +0300 Subject: [PATCH 11/44] remove extra line --- src/vm/wren_compiler.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index c7f0de37..3937d1d2 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -3446,12 +3446,11 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, parser.printErrors = printErrors; parser.hasError = false; - // Read the first token. nextToken(&parser); int numExistingVariables = module->variables.count; - + Compiler compiler; initCompiler(&compiler, &parser, NULL, true); ignoreNewlines(&compiler); From 492763205ba2b4dddcf40102f2ac4f75b3c952f2 Mon Sep 17 00:00:00 2001 From: Freddie Ridell Date: Thu, 29 Mar 2018 14:26:45 +0100 Subject: [PATCH 12/44] introduced docs auto publish step to travisci --- .travis.yml | 11 ++++++++++- Makefile | 1 + util/deployGHP.sh | 23 +++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 util/deployGHP.sh diff --git a/.travis.yml b/.travis.yml index 938b01f5..792e942c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,19 @@ language: c os: - - osx - linux + - osx compiler: - gcc - clang env: - WREN_OPTIONS="" CI_ARCHS="ci_32 ci_64" - WREN_OPTIONS="-DWREN_NAN_TAGGING=0" CI_ARCHS="ci_64" + +jobs: + include: + - stage: deploy + script: bash util/deployGHP.sh + # Travis VMs are 64-bit but we compile both for 32 and 64 bit. To enable the # 32-bit builds to work, we need gcc-multilib. addons: @@ -15,5 +21,8 @@ addons: packages: - gcc-multilib - g++-multilib + - python3-markdown + - ruby-sass + sudo: false # Enable container-based builds. script: make WREN_CFLAGS=${WREN_OPTIONS} ${CI_ARCHS} diff --git a/Makefile b/Makefile index f952c283..21791bdc 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,7 @@ benchmark: release # Generate the Wren site. docs: + mkdir -p build $(V) ./util/generate_docs.py # Continuously generate and serve the Wren site. diff --git a/util/deployGHP.sh b/util/deployGHP.sh new file mode 100644 index 00000000..54ecb01a --- /dev/null +++ b/util/deployGHP.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +make gh-pages + +git clone https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} gh-pages-repo +cd gh-pages-repo +git checkout gh-pages + +rm -rf * +cp -r ../build/gh-pages/* . + +git status +ls + +if ! $( git diff-index --quiet HEAD ) ; then + git config user.name "Travis CI" + git config user.email "$COMMIT_AUTHOR_EMAIL" + git add -A . + git commit -m "Deploy to GitHub Pages: ${SHA}" + git push +fi From 4442b374118560e7758b2ab3d89d6347bbe63c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarom=C3=ADr=20M=C3=BCller?= Date: Thu, 5 Apr 2018 23:02:30 +0200 Subject: [PATCH 13/44] fixed typo, function declares data, body uses file --- doc/site/embedding/storing-c-data.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/site/embedding/storing-c-data.markdown b/doc/site/embedding/storing-c-data.markdown index 9c1e630d..4ff6422c 100644 --- a/doc/site/embedding/storing-c-data.markdown +++ b/doc/site/embedding/storing-c-data.markdown @@ -270,7 +270,7 @@ and closes the file: :::c void fileFinalize(void* data) { - closeFile((FILE**)file); + closeFile((FILE**) data); } It uses this little utility function: From 372cd3e1979d51385dddf690047ce637e2115bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 6 Apr 2018 10:34:12 +0200 Subject: [PATCH 14/44] Small documentation fixes --- doc/site/classes.markdown | 2 +- doc/site/concurrency.markdown | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/site/classes.markdown b/doc/site/classes.markdown index e436d3e6..2b5659fe 100644 --- a/doc/site/classes.markdown +++ b/doc/site/classes.markdown @@ -430,7 +430,7 @@ They can be used from static methods: :::wren Foo.setFromStatic("first") - Foo.bar.printFromStatic() //> first + Foo.printFromStatic() //> first And also instance methods. When you do so, there is still only one static field shared among all instances of the class: diff --git a/doc/site/concurrency.markdown b/doc/site/concurrency.markdown index a2839ab3..812184e2 100644 --- a/doc/site/concurrency.markdown +++ b/doc/site/concurrency.markdown @@ -148,14 +148,14 @@ method calls — the entire callstack — gets suspended. For example: :::wren var fiber = Fiber.new { - (1..10).map {|i| + (1..10).each {|i| Fiber.yield(i) } } Here, we're calling `yield()` from within a [function](functions.html) being -passed to the `map()` method. This works fine in Wren because that inner -`yield()` call will suspend the call to `map()` and the function passed to it +passed to the `each()` method. This works fine in Wren because that inner +`yield()` call will suspend the call to `each()` and the function passed to it as a callback. ## Transferring control From f7a61df634d32b7ef8e5202b98071c3ee8ba0229 Mon Sep 17 00:00:00 2001 From: Freddie Ridell Date: Fri, 6 Apr 2018 19:53:53 +0100 Subject: [PATCH 15/44] only upload docs from the master branch --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 792e942c..4905b166 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ jobs: include: - stage: deploy script: bash util/deployGHP.sh + if: branch = master # Travis VMs are 64-bit but we compile both for 32 and 64 bit. To enable the # 32-bit builds to work, we need gcc-multilib. From 67ed36e79a565e45ace7725c871a5d9231e116e0 Mon Sep 17 00:00:00 2001 From: Freddie Ridell Date: Fri, 6 Apr 2018 20:04:34 +0100 Subject: [PATCH 16/44] don't publish docs from PRs, only from merges into master --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4905b166..6e206c80 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ jobs: include: - stage: deploy script: bash util/deployGHP.sh - if: branch = master + if: branch = master AND NOT type IN (pull_request) # Travis VMs are 64-bit but we compile both for 32 and 64 bit. To enable the # 32-bit builds to work, we need gcc-multilib. From 4034a6d65a99b2daf85655a6d344f0ec243d1063 Mon Sep 17 00:00:00 2001 From: jclc Date: Sat, 7 Apr 2018 06:04:32 +0300 Subject: [PATCH 17/44] crunched BOM skipping --- src/vm/wren_compiler.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 3937d1d2..75d0136e 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -3417,26 +3417,25 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, bool isExpression, bool printErrors) { // Skip potential UTF-8 BOM - size_t sourceOffset = 0; - if (source[0] == (char) 0xef && source[1] == (char) 0xbb && source[2] == (char) 0xbf) + if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) { - sourceOffset = 3; + source += 3; } Parser parser; parser.vm = vm; parser.module = module; - parser.source = source + sourceOffset; + parser.source = source; - parser.tokenStart = source + sourceOffset; - parser.currentChar = source + sourceOffset; + parser.tokenStart = source; + parser.currentChar = source; parser.currentLine = 1; parser.numParens = 0; // Zero-init the current token. This will get copied to previous when // advance() is called below. parser.current.type = TOKEN_ERROR; - parser.current.start = source + sourceOffset; + parser.current.start = source; parser.current.length = 0; parser.current.line = 0; parser.current.value = UNDEFINED_VAL; From e66115c9fc086d3dafdbf20141999e707a7667df Mon Sep 17 00:00:00 2001 From: jclc Date: Tue, 10 Apr 2018 18:53:56 +0300 Subject: [PATCH 18/44] add regression test for #520 --- test/regression/520.wren | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/regression/520.wren diff --git a/test/regression/520.wren b/test/regression/520.wren new file mode 100644 index 00000000..6eddf921 --- /dev/null +++ b/test/regression/520.wren @@ -0,0 +1,2 @@ +// This file should have a UTF-8 byte order mark +System.print("ok") // expect: ok From 4bc3e6f424715c4fbd7d7e30df1a41d4ee47c789 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Mon, 23 Apr 2018 16:48:47 -0700 Subject: [PATCH 19/44] Fix LICENSE to match https://choosealicense.com/licenses/mit/. That way GitHub and other tools correctly recognize the license. There is no substantive change to the license itself. --- LICENSE | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/LICENSE b/LICENSE index 6185cc18..ad6a44d6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,23 +1,21 @@ -Wren uses the MIT License: +MIT License -Copyright (c) 2013-2016 Robert Nystrom +Copyright (c) 2013 Robert Nystrom -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. (As clarification, there is no -requirement that the copyright notice and permission be included in binary -distributions of the Software.) +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From c5ce6fac46b43bad593a8a9f4fc4b9d4918ac73f Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Fri, 27 Apr 2018 08:20:49 -0700 Subject: [PATCH 20/44] Fix local variable declarations in the REPL. A statement like: for (i in 1..2) When run in the REPL declares a local variable ("i"), but not inside a function or method body. This hit a corner case in the compiler where it didn't have the correct slot indexes set up. That corner case is because sometimes when you compile a chunk, local slot zero is pre-allocated -- either to refer to "this" or to hold the closure for a function so that it doesn't get GCed while running. But if you're compiling top-level code, that slot isn't allocated. But top level code for the REPL *should* be, because that gets invoked like a function. To simplify things, *every* compiled chunk now pre-allocates slot zero. That way, there are fewer cases to keep in mind. Also fixed an issue where a GC during an import could collected the imported module body's closure. Fix #456. --- src/vm/wren_compiler.c | 60 +++++++++---------- src/vm/wren_core.c | 10 +--- src/vm/wren_value.c | 30 ++++++---- src/vm/wren_value.h | 4 -- src/vm/wren_vm.c | 13 ++-- test/language/variable/many_locals.wren | 4 +- .../variable/many_nonsimultaneous_locals.wren | 10 ++-- test/language/variable/too_many_locals.wren | 4 +- .../variable/too_many_locals_nested.wren | 4 +- 9 files changed, 66 insertions(+), 73 deletions(-) diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 66af1432..582548da 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -498,7 +498,7 @@ static int addConstant(Compiler* compiler, Value constant) // Initializes [compiler]. static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent, - bool isFunction) + bool isMethod) { compiler->parser = parser; compiler->parent = parent; @@ -512,41 +512,41 @@ static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent, parser->vm->compiler = compiler; + // Declare a local slot for either the closure or method receiver so that we + // don't try to reuse that slot for a user-defined local variable. For + // methods, we name it "this", so that we can resolve references to that like + // a normal variable. For functions, they have no explicit "this", so we use + // an empty name. That way references to "this" inside a function walks up + // the parent chain to find a method enclosing the function whose "this" we + // can close over. + compiler->numLocals = 1; + compiler->numSlots = compiler->numLocals; + + if (isMethod) + { + compiler->locals[0].name = "this"; + compiler->locals[0].length = 4; + } + else + { + compiler->locals[0].name = NULL; + compiler->locals[0].length = 0; + } + + compiler->locals[0].depth = -1; + compiler->locals[0].isUpvalue = false; + if (parent == NULL) { - compiler->numLocals = 0; - // Compiling top-level code, so the initial scope is module-level. compiler->scopeDepth = -1; } else { - // Declare a fake local variable for the receiver so that it's slot in the - // stack is taken. For methods, we call this "this", so that we can resolve - // references to that like a normal variable. For functions, they have no - // explicit "this". So we pick a bogus name. That way references to "this" - // inside a function will try to walk up the parent chain to find a method - // enclosing the function whose "this" we can close over. - compiler->numLocals = 1; - if (isFunction) - { - compiler->locals[0].name = NULL; - compiler->locals[0].length = 0; - } - else - { - compiler->locals[0].name = "this"; - compiler->locals[0].length = 4; - } - compiler->locals[0].depth = -1; - compiler->locals[0].isUpvalue = false; - - // The initial scope for function or method is a local scope. + // The initial scope for functions and methods is local scope. compiler->scopeDepth = 0; } - compiler->numSlots = compiler->numLocals; - compiler->fn = wrenNewFunction(parser->vm, parser->module, compiler->numLocals); } @@ -1867,7 +1867,7 @@ static void methodCall(Compiler* compiler, Code instruction, called.arity++; Compiler fnCompiler; - initCompiler(&fnCompiler, compiler->parser, compiler, true); + initCompiler(&fnCompiler, compiler->parser, compiler, false); // Make a dummy signature to track the arity. Signature fnSignature = { "", 0, SIG_METHOD, 0 }; @@ -3080,7 +3080,7 @@ static void createConstructor(Compiler* compiler, Signature* signature, int initializerSymbol) { Compiler methodCompiler; - initCompiler(&methodCompiler, compiler->parser, compiler, false); + initCompiler(&methodCompiler, compiler->parser, compiler, true); // Allocate the instance. emitOp(&methodCompiler, compiler->enclosingClass->isForeign @@ -3166,7 +3166,7 @@ static bool method(Compiler* compiler, Variable classVariable) compiler->enclosingClass->signature = &signature; Compiler methodCompiler; - initCompiler(&methodCompiler, compiler->parser, compiler, false); + initCompiler(&methodCompiler, compiler->parser, compiler, true); // Compile the method signature. signatureFn(&methodCompiler, &signature); @@ -3445,7 +3445,7 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, int numExistingVariables = module->variables.count; Compiler compiler; - initCompiler(&compiler, &parser, NULL, true); + initCompiler(&compiler, &parser, NULL, false); ignoreNewlines(&compiler); if (isExpression) diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index a827b057..f45823b3 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -59,15 +59,7 @@ DEF_PRIMITIVE(fiber_new) RETURN_ERROR("Function cannot take more than one parameter."); } - ObjFiber* newFiber = wrenNewFiber(vm, closure); - - // The compiler expects the first slot of a function to hold the receiver. - // Since a fiber's stack is invoked directly, it doesn't have one, so put it - // in here. - newFiber->stack[0] = NULL_VAL; - newFiber->stackTop++; - - RETURN_OBJ(newFiber); + RETURN_OBJ(wrenNewFiber(vm, closure)); } DEF_PRIMITIVE(fiber_abort) diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index e16f25f4..548a8008 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -159,27 +159,31 @@ ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure) ObjFiber* fiber = ALLOCATE(vm, ObjFiber); initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass); + + fiber->stack = stack; + fiber->stackTop = fiber->stack; + fiber->stackCapacity = stackCapacity; + fiber->frames = frames; fiber->frameCapacity = INITIAL_CALL_FRAMES; - fiber->stack = stack; - fiber->stackCapacity = stackCapacity; - wrenResetFiber(vm, fiber, closure); + fiber->numFrames = 0; - return fiber; -} - -void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, ObjClosure* closure) -{ - // Reset everything. - fiber->stackTop = fiber->stack; fiber->openUpvalues = NULL; fiber->caller = NULL; fiber->error = NULL_VAL; fiber->callerIsTrying = false; - fiber->numFrames = 0; + + if (closure != NULL) + { + // Initialize the first call frame. + wrenAppendCallFrame(vm, fiber, closure, fiber->stack); - // Initialize the first call frame. - if (closure != NULL) wrenAppendCallFrame(vm, fiber, closure, fiber->stack); + // The first slot always holds the closure. + fiber->stackTop[0] = OBJ_VAL(closure); + fiber->stackTop++; + } + + return fiber; } void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed) diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index ffb79f08..d6d5ff43 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -628,10 +628,6 @@ ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn); // Creates a new fiber object that will invoke [closure]. ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure); -// Resets [fiber] back to an initial state where it is ready to invoke -// [closure]. -void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, ObjClosure* closure); - // Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at // [stackStart]. static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber, diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 33c18e45..bc7e9e7b 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -1249,17 +1249,18 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) Value name = fn->constants.data[READ_SHORT()]; // Make a slot on the stack for the module's fiber to place the return - // value. It will be popped after this fiber is resumed. - PUSH(NULL_VAL); + // value. It will be popped after this fiber is resumed. Store the + // imported module's closure in the slot in case a GC happens when + // invoking the closure. + PUSH(wrenImportModule(vm, name)); - Value result = wrenImportModule(vm, name); if (!IS_NULL(fiber->error)) RUNTIME_ERROR(); // If we get a closure, call it to execute the module body. - if (IS_CLOSURE(result)) + if (IS_CLOSURE(PEEK())) { STORE_FRAME(); - ObjClosure* closure = AS_CLOSURE(result); + ObjClosure* closure = AS_CLOSURE(PEEK()); callFunction(vm, fiber, closure, 1); LOAD_FRAME(); } @@ -1417,7 +1418,7 @@ WrenInterpretResult wrenInterpretInModule(WrenVM* vm, const char* module, wrenPushRoot(vm, (Obj*)closure); ObjFiber* fiber = wrenNewFiber(vm, closure); wrenPopRoot(vm); // closure. - + return runInterpreter(vm, fiber); } diff --git a/test/language/variable/many_locals.wren b/test/language/variable/many_locals.wren index 97cc20c7..f2716d66 100644 --- a/test/language/variable/many_locals.wren +++ b/test/language/variable/many_locals.wren @@ -1,6 +1,6 @@ { - var a0 = "value" - var a1 = a0 + // Slot zero is always taken to hold the closure or receiver. + var a1 = "value" var a2 = a1 var a3 = a2 var a4 = a3 diff --git a/test/language/variable/many_nonsimultaneous_locals.wren b/test/language/variable/many_nonsimultaneous_locals.wren index 8320c1e9..fde7a127 100644 --- a/test/language/variable/many_nonsimultaneous_locals.wren +++ b/test/language/variable/many_nonsimultaneous_locals.wren @@ -1,10 +1,10 @@ -// Can have more than 256 local variables in a local scope, as long as they +// Can have more than 255 local variables in a local scope, as long as they // aren't all in scope at the same time. { { - var a0 = "value a" - var a1 = a0 + // Slot zero is always taken to hold the closure or receiver. + var a1 = "value a" var a2 = a1 var a3 = a2 var a4 = a3 @@ -263,8 +263,8 @@ } { - var b0 = "value b" - var b1 = b0 + // Slot zero is always taken to hold the closure or receiver. + var b1 = "value b" var b2 = b1 var b3 = b2 var b4 = b3 diff --git a/test/language/variable/too_many_locals.wren b/test/language/variable/too_many_locals.wren index 0c942ac0..3e6bf6d1 100644 --- a/test/language/variable/too_many_locals.wren +++ b/test/language/variable/too_many_locals.wren @@ -1,6 +1,6 @@ { - var a0 = "value" - var a1 = a0 + // Slot zero is always taken to hold the closure or receiver. + var a1 = "value" var a2 = a1 var a3 = a2 var a4 = a3 diff --git a/test/language/variable/too_many_locals_nested.wren b/test/language/variable/too_many_locals_nested.wren index 0c9736ac..9cb4801e 100644 --- a/test/language/variable/too_many_locals_nested.wren +++ b/test/language/variable/too_many_locals_nested.wren @@ -1,6 +1,6 @@ { - var a0 = "value" - var a1 = a0 + // Slot zero is always taken to hold the closure or receiver. + var a1 = "value" var a2 = a1 var a3 = a2 var a4 = a3 From 8fae8e4f1e490888e2cc9b2ea6b8e0d0ff9dd60f Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Fri, 27 Apr 2018 09:13:40 -0700 Subject: [PATCH 21/44] Don't overflow signature string if there are too many parameters. Fix #494. --- src/vm/wren_compiler.c | 6 +++++- test/regression/494.wren | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/regression/494.wren diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 582548da..d90273bf 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -1704,7 +1704,11 @@ static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length, int numParams, char leftBracket, char rightBracket) { name[(*length)++] = leftBracket; - for (int i = 0; i < numParams; i++) + + // This function may be called with too many parameters. When that happens, + // a compile error has already been reported, but we need to make sure we + // don't overflow the string too, hence the MAX_PARAMETERS check. + for (int i = 0; i < numParams && i < MAX_PARAMETERS; i++) { if (i > 0) name[(*length)++] = ','; name[(*length)++] = '_'; diff --git a/test/regression/494.wren b/test/regression/494.wren new file mode 100644 index 00000000..eea92e40 --- /dev/null +++ b/test/regression/494.wren @@ -0,0 +1,3 @@ +0[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +// expect error line 1 +// expect error line 4 From d03ef9e8b0cb5d29cc73bde53cb6c86cd71d30f2 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 28 Apr 2018 10:07:04 -0700 Subject: [PATCH 22/44] Move test and tweak code a bit. --- src/vm/wren_compiler.c | 9 +++------ test/{regression/520.wren => language/bom.wren} | 0 2 files changed, 3 insertions(+), 6 deletions(-) rename test/{regression/520.wren => language/bom.wren} (100%) diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index c6a968c0..d7496ac5 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -3420,12 +3420,9 @@ void definition(Compiler* compiler) ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, bool isExpression, bool printErrors) { - // Skip potential UTF-8 BOM - if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) - { - source += 3; - } - + // Skip the UTF-8 BOM if there is one. + if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3; + Parser parser; parser.vm = vm; parser.module = module; diff --git a/test/regression/520.wren b/test/language/bom.wren similarity index 100% rename from test/regression/520.wren rename to test/language/bom.wren From 41a56446c607cae04061fe4c15fa70894ff854ee Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 28 Apr 2018 12:13:03 -0700 Subject: [PATCH 23/44] Refine a few things: - Fix some doc comments. - Inline comparing two ObjStrings, since it's only used in one place. Also, this avoids a redundant identity check. - Move the forward declarations of the object types out of wren_common.h. Instead, I just added the one needed forward declaration of ObjString in wren_utils.h. It's a little inelegant, but it feels weird to me to expose all of the object types in wren_common.h when they logically belong to wren_value.h and most of the types aren't problematic. - Fix a bug where field symbol tables weren't being marked. If a GC happened while compiling a class, field strings got freed. --- src/vm/wren_common.h | 13 -------- src/vm/wren_compiler.c | 6 ++++ src/vm/wren_utils.c | 4 +-- src/vm/wren_utils.h | 4 +++ src/vm/wren_value.c | 5 +++- src/vm/wren_value.h | 68 +++++++++++++++++++----------------------- 6 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/vm/wren_common.h b/src/vm/wren_common.h index f22caee4..c3b5682c 100644 --- a/src/vm/wren_common.h +++ b/src/vm/wren_common.h @@ -199,17 +199,4 @@ #endif -typedef struct sObjClass ObjClass; -typedef struct sObjClosure ObjClosure; -typedef struct sObjFiber ObjFiber; -typedef struct sObjForeign ObjForeign; -typedef struct sObjFn ObjFn; -typedef struct sObjInstance ObjInstance; -typedef struct sObjList ObjList; -typedef struct sObjMap ObjMap; -typedef struct sObjModule ObjModule; -typedef struct sObjRange ObjRange; -typedef struct sObjString ObjString; -typedef struct sObjUpvalue ObjUpvalue; - #endif diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 3e2dfaee..e8ff87cc 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -3569,6 +3569,12 @@ void wrenMarkCompiler(WrenVM* vm, Compiler* compiler) { wrenGrayObj(vm, (Obj*)compiler->fn); wrenGrayObj(vm, (Obj*)compiler->constants); + + if (compiler->enclosingClass != NULL) + { + wrenBlackenSymbolTable(vm, &compiler->enclosingClass->fields); + } + compiler = compiler->parent; } while (compiler != NULL); diff --git a/src/vm/wren_utils.c b/src/vm/wren_utils.c index e179cef5..0989fefe 100644 --- a/src/vm/wren_utils.c +++ b/src/vm/wren_utils.c @@ -24,7 +24,7 @@ int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, wrenPushRoot(vm, &symbol->obj); wrenStringBufferWrite(vm, symbols, symbol); - wrenPopRoot(vm); // symbol + wrenPopRoot(vm); return symbols->count - 1; } @@ -47,7 +47,7 @@ int wrenSymbolTableFind(const SymbolTable* symbols, // TODO: O(n). Do something better. for (int i = 0; i < symbols->count; i++) { - if(wrenStringEqualStrLength(symbols->data[i], name, length)) return i; + if (wrenStringEqualsCString(symbols->data[i], name, length)) return i; } return -1; diff --git a/src/vm/wren_utils.h b/src/vm/wren_utils.h index 959f8526..4e43524d 100644 --- a/src/vm/wren_utils.h +++ b/src/vm/wren_utils.h @@ -6,6 +6,10 @@ // Reusable data structures and other utility functions. +// Forward declare this here to break a cycle between wren_utils.h and +// wren_value.h. +typedef struct sObjString ObjString; + // We need buffers of a few different types. To avoid lots of casting between // void* and back, we'll use the preprocessor as a poor man's generics and let // it generate a few type-specific ones. diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index b1f3e47c..fc645359 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -1299,7 +1299,10 @@ bool wrenValuesEqual(Value a, Value b) case OBJ_STRING: { - return wrenStringsEqual((ObjString*)aObj, (ObjString*)bObj); + ObjString* aString = (ObjString*)aObj; + ObjString* bString = (ObjString*)bObj; + return aString->hash == bString->hash && + wrenStringEqualsCString(aString, bString->value, bString->length); } default: diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index 1b89b030..31f19f50 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -100,6 +100,8 @@ typedef enum { OBJ_UPVALUE } ObjType; +typedef struct sObjClass ObjClass; + // Base struct for all heap-allocated objects. typedef struct sObj Obj; struct sObj @@ -171,7 +173,7 @@ struct sObjString // be closed. When that happens, the value gets copied off the stack into the // upvalue itself. That way, it can have a longer lifetime than the stack // variable. -struct sObjUpvalue +typedef struct sObjUpvalue { // The object header. Note that upvalues have this because they are garbage // collected, but they are not first class Wren objects. @@ -188,7 +190,7 @@ struct sObjUpvalue // Open upvalues are stored in a linked list by the fiber. This points to the // next upvalue in that list. struct sObjUpvalue* next; -}; +} ObjUpvalue; // The type of a primitive function. // @@ -217,7 +219,7 @@ typedef struct // // While this is an Obj and is managed by the GC, it never appears as a // first-class object in Wren. -struct sObjModule +typedef struct { Obj obj; @@ -230,7 +232,7 @@ struct sObjModule // The name of the module. ObjString* name; -}; +} ObjModule; // A function object. It wraps and owns the bytecode and other debug information // for a callable chunk of code. @@ -240,7 +242,7 @@ struct sObjModule // representation of a function. This isn't strictly necessary if they function // has no upvalues, but lets the rest of the VM assume all called objects will // be closures. -struct sObjFn +typedef struct { Obj obj; @@ -261,11 +263,11 @@ struct sObjFn // only be set for fns, and not ObjFns that represent methods or scripts. int arity; FnDebug* debug; -}; +} ObjFn; // An instance of a first-class function and the environment it has closed over. // Unlike [ObjFn], this has captured the upvalues that the function accesses. -struct sObjClosure +typedef struct { Obj obj; @@ -274,7 +276,7 @@ struct sObjClosure // The upvalues this function has closed over. ObjUpvalue* upvalues[FLEXIBLE_ARRAY]; -}; +} ObjClosure; typedef struct { @@ -291,7 +293,7 @@ typedef struct Value* stackStart; } CallFrame; -struct sObjFiber +typedef struct sObjFiber { Obj obj; @@ -333,7 +335,7 @@ struct sObjFiber // In that case, if this fiber fails with an error, the error will be given // to the caller. bool callerIsTrying; -}; +} ObjFiber; typedef enum { @@ -395,25 +397,25 @@ struct sObjClass ObjString* name; }; -struct sObjForeign +typedef struct { Obj obj; uint8_t data[FLEXIBLE_ARRAY]; -}; +} ObjForeign; -struct sObjInstance +typedef struct { Obj obj; Value fields[FLEXIBLE_ARRAY]; -}; +} ObjInstance; -struct sObjList +typedef struct { Obj obj; // The elements in the list. ValueBuffer elements; -}; +} ObjList; typedef struct { @@ -443,7 +445,7 @@ typedef struct // for a key, we will continue past tombstones, because the desired key may be // found after them if the key that was removed was part of a prior collision. // When the array gets resized, all tombstones are discarded. -struct sObjMap +typedef struct { Obj obj; @@ -455,9 +457,9 @@ struct sObjMap // Pointer to a contiguous array of [capacity] entries. MapEntry* entries; -}; +} ObjMap; -struct sObjRange +typedef struct { Obj obj; @@ -469,7 +471,7 @@ struct sObjRange // True if [to] is included in the range. bool isInclusive; -}; +} ObjRange; // An IEEE 754 double-precision float is a 64-bit value with bits laid out like: // @@ -729,7 +731,15 @@ Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index); // Search for the first occurence of [needle] within [haystack] and returns its // zero-based offset. Returns `UINT32_MAX` if [haystack] does not contain // [needle]. -uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, uint32_t startIndex); +uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, + uint32_t startIndex); + +// Returns true if [a] and [b] represent the same string. +static inline bool wrenStringEqualsCString(const ObjString* a, + const char* b, size_t length) +{ + return a->length == length && memcmp(a->value, b, length) == 0; +} // Creates a new open upvalue pointing to [value] on the stack. ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value); @@ -782,22 +792,6 @@ static inline bool wrenValuesSame(Value a, Value b) // other values are equal if they are identical objects. bool wrenValuesEqual(Value a, Value b); -// Returns true is [a] and [str] represent the same string. -static inline bool wrenStringEqualStrLength(const ObjString* a, - const char* str, size_t length) -{ - return a->length == length && - memcmp(a->value, str, length) == 0; -} - -// Returns true is [a] and [b] represent the same string. -static inline bool wrenStringsEqual(const ObjString* a, const ObjString* b) -{ - return a == b || - (a->hash == b->hash && - wrenStringEqualStrLength(a, b->value, b->length)); -} - // Returns true if [value] is a bool. Do not call this directly, instead use // [IS_BOOL]. static inline bool wrenIsBool(Value value) From c4ae0f5c5935cdd6b100823f979097b013ce59d1 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 28 Apr 2018 16:38:09 -0700 Subject: [PATCH 24/44] Be a little more conservative with some string operations. This should hopefully fix #531, though in practice the previous code should have been safe too. --- src/cli/modules.c | 2 +- src/vm/wren_compiler.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/modules.c b/src/cli/modules.c index 47b6286d..4f65d1cb 100644 --- a/src/cli/modules.c +++ b/src/cli/modules.c @@ -241,7 +241,7 @@ char* readBuiltInModule(const char* name) size_t length = strlen(*module->source); char* copy = (char*)malloc(length + 1); - strncpy(copy, *module->source, length + 1); + memcpy(copy, *module->source, length + 1); return copy; } diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index e8ff87cc..d025b6e3 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -48,7 +48,7 @@ // is kind of hairy, but fortunately we can control what the longest possible // message is and handle that. Ideally, we'd use `snprintf()`, but that's not // available in standard C++98. -#define ERROR_MESSAGE_SIZE (60 + MAX_VARIABLE_NAME + 15) +#define ERROR_MESSAGE_SIZE (80 + MAX_VARIABLE_NAME + 15) typedef enum { From 40c927f4402bb6ff74fe8aa257bf8042eeff6544 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 28 Apr 2018 16:54:33 -0700 Subject: [PATCH 25/44] Make sure we consume all the input when compiling a single expression. Fix #528. --- src/vm/wren_compiler.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index d025b6e3..bd8ab724 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -3458,6 +3458,7 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, if (isExpression) { expression(&compiler); + consume(&compiler, TOKEN_EOF, "Expect end of expression."); } else { @@ -3465,7 +3466,7 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, { definition(&compiler); - // If there is no newline, it must be the end of the block on the same line. + // If there is no newline, it must be the end of file on the same line. if (!matchLine(&compiler)) { consume(&compiler, TOKEN_EOF, "Expect end of file."); From 4c9209c1b26223b48a9f8d96159c88e20e1d8ff6 Mon Sep 17 00:00:00 2001 From: Garrett van Wageningen Date: Sun, 3 Jun 2018 23:06:40 -0500 Subject: [PATCH 26/44] Update file.markdown minor fix to close() language --- doc/site/modules/io/file.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/site/modules/io/file.markdown b/doc/site/modules/io/file.markdown index 595eee1c..90ff896b 100644 --- a/doc/site/modules/io/file.markdown +++ b/doc/site/modules/io/file.markdown @@ -97,7 +97,7 @@ The size of the contents of the file in bytes. ### **close**() -Closes the file. After calling this, you can read or write from it. +Closes the file. After calling this, you can't read or write from it. ### **readBytes**(count) From 18f74d89c453f05c651994a6dd395e0b6f406ac7 Mon Sep 17 00:00:00 2001 From: Charlotte Koch Date: Tue, 5 Jun 2018 13:27:45 -0700 Subject: [PATCH 27/44] Don't forget to #undef FINALIZER --- src/cli/modules.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/modules.c b/src/cli/modules.c index 4f65d1cb..4e015e4e 100644 --- a/src/cli/modules.c +++ b/src/cli/modules.c @@ -192,6 +192,7 @@ static ModuleRegistry modules[] = #undef END_CLASS #undef METHOD #undef STATIC_METHOD +#undef FINALIZER // Looks for a built-in module with [name]. // From 5356ccb7602c8dfcc2ff98567d826ee26471ce6c Mon Sep 17 00:00:00 2001 From: Charlotte Koch Date: Thu, 14 Jun 2018 00:33:59 -0700 Subject: [PATCH 28/44] Add myself to the AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 41abe44e..17ccd36a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,3 +21,4 @@ Max Ferguson Sven Bergström Kyle Charters Marshall Bowers +Charlotte Koch From ad9a0e13ac4ea272ed433781cddcddf6f5061386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kozakiewicz?= Date: Thu, 12 Jul 2018 21:50:23 +0200 Subject: [PATCH 29/44] Small improvements * Safer definitions in io.c * Easier to understand information in repl.wren * Added my e-mail; Contributing > Hacking on the VM --- AUTHORS | 1 + src/module/io.c | 10 ++++++++++ src/module/repl.wren | 2 +- src/module/repl.wren.inc | 2 +- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 41abe44e..080ba4b9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,3 +21,4 @@ Max Ferguson Sven Bergström Kyle Charters Marshall Bowers +Michal Kozakiewicz diff --git a/src/module/io.c b/src/module/io.c index ee4ee895..7ef3d265 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -16,11 +16,21 @@ #include // Map to Windows permission flags. + #ifndef S_IRUSR #define S_IRUSR _S_IREAD + #endif + + #ifndef S_IWUSR #define S_IWUSR _S_IWRITE + #endif + #ifndef S_ISREG #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) + #endif + + #ifndef S_ISDIR #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) + #endif // Not supported on Windows. #define O_SYNC 0 diff --git a/src/module/repl.wren b/src/module/repl.wren index 6a77478e..eec22036 100644 --- a/src/module/repl.wren +++ b/src/module/repl.wren @@ -73,7 +73,7 @@ class Repl { insertChar(byte) } else { // TODO: Other shortcuts? - System.print("Unhandled byte: %(byte)") + System.print("Unhandled key-code [dec]: %(byte)") } return false diff --git a/src/module/repl.wren.inc b/src/module/repl.wren.inc index dcd923da..7ef78c5a 100644 --- a/src/module/repl.wren.inc +++ b/src/module/repl.wren.inc @@ -75,7 +75,7 @@ static const char* replModuleSource = " insertChar(byte)\n" " } else {\n" " // TODO: Other shortcuts?\n" -" System.print(\"Unhandled byte: %(byte)\")\n" +" System.print(\"Unhandled key-code [dec]: %(byte)\")\n" " }\n" "\n" " return false\n" From 1f93e16fb20dbb36cce4bf6cb0e7d6c45371960b Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Fri, 13 Jul 2018 07:19:06 -0700 Subject: [PATCH 30/44] Provide actual soname when building shared lib on Linux. Fix #572. --- util/wren.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/wren.mk b/util/wren.mk index f37fa82b..febb06bb 100644 --- a/util/wren.mk +++ b/util/wren.mk @@ -107,7 +107,7 @@ endif ifneq (,$(findstring darwin,$(OS))) SHARED_EXT := dylib else - SHARED_LIB_FLAGS := -Wl,-soname,$@.so + SHARED_LIB_FLAGS := -Wl,-soname,libwren.so SHARED_EXT := so # Link in the right libraries needed by libuv on Windows and Linux. From c12076997701a80f0c6461e4ea9803d99d7b6ac0 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Fri, 13 Jul 2018 08:02:37 -0700 Subject: [PATCH 31/44] Make some tweaks to doc deploy script. - Rename file to match other naming conventions. - Simplify condition a little. --- .travis.yml | 5 +++-- util/{deployGHP.sh => deploy_docs_from_travis.sh} | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename util/{deployGHP.sh => deploy_docs_from_travis.sh} (100%) diff --git a/.travis.yml b/.travis.yml index 6e206c80..30ee619f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,9 @@ env: jobs: include: - stage: deploy - script: bash util/deployGHP.sh - if: branch = master AND NOT type IN (pull_request) + script: bash util/deploy_docs_from_travis.sh + # Only deploy commits that land on master. + if: branch = master and type = push # Travis VMs are 64-bit but we compile both for 32 and 64 bit. To enable the # 32-bit builds to work, we need gcc-multilib. diff --git a/util/deployGHP.sh b/util/deploy_docs_from_travis.sh similarity index 100% rename from util/deployGHP.sh rename to util/deploy_docs_from_travis.sh From 08b5492362ba9f54f3821acede0634fd79c8fc5e Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Fri, 13 Jul 2018 09:15:26 -0700 Subject: [PATCH 32/44] Turn off doc deploying for now. --- .travis.yml | 13 +++++++------ util/deploy_docs_from_travis.sh | 10 +++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 30ee619f..c30d4206 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,13 @@ env: - WREN_OPTIONS="" CI_ARCHS="ci_32 ci_64" - WREN_OPTIONS="-DWREN_NAN_TAGGING=0" CI_ARCHS="ci_64" -jobs: - include: - - stage: deploy - script: bash util/deploy_docs_from_travis.sh - # Only deploy commits that land on master. - if: branch = master and type = push +# TODO: Enable this once the TODOs in util/deploy_docs_from_travis.sh are fixed. +# jobs: +# include: +# - stage: deploy +# script: bash util/deploy_docs_from_travis.sh +# # Only deploy commits that land on master. +# if: branch = master and type = push # Travis VMs are 64-bit but we compile both for 32 and 64 bit. To enable the # 32-bit builds to work, we need gcc-multilib. diff --git a/util/deploy_docs_from_travis.sh b/util/deploy_docs_from_travis.sh index 54ecb01a..1cb8d6a1 100644 --- a/util/deploy_docs_from_travis.sh +++ b/util/deploy_docs_from_travis.sh @@ -3,6 +3,8 @@ set -e make gh-pages +# TODO: This strips the syntax highlighting because the custom pygments lexer +# isn't installed. git clone https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} gh-pages-repo cd gh-pages-repo @@ -11,13 +13,15 @@ git checkout gh-pages rm -rf * cp -r ../build/gh-pages/* . +# TODO: Restore CNAME file that gets deleted by `rm -rf *`. + git status -ls +ls if ! $( git diff-index --quiet HEAD ) ; then git config user.name "Travis CI" git config user.email "$COMMIT_AUTHOR_EMAIL" - git add -A . + git add --all . git commit -m "Deploy to GitHub Pages: ${SHA}" - git push + git push fi From 5fff6935306100f19b247bc927d7342dd4cef584 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 10:35:45 -0700 Subject: [PATCH 33/44] Testing fix to doc deploy script. --- .travis.yml | 14 +++++++------- util/deploy_docs_from_travis.sh | 29 ++++++++++++++++++----------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index c30d4206..0306bff0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,13 @@ env: - WREN_OPTIONS="" CI_ARCHS="ci_32 ci_64" - WREN_OPTIONS="-DWREN_NAN_TAGGING=0" CI_ARCHS="ci_64" -# TODO: Enable this once the TODOs in util/deploy_docs_from_travis.sh are fixed. -# jobs: -# include: -# - stage: deploy -# script: bash util/deploy_docs_from_travis.sh -# # Only deploy commits that land on master. -# if: branch = master and type = push +# Automatically build and deploy docs. +jobs: + include: + - stage: deploy + script: bash util/deploy_docs_from_travis.sh + # Only deploy commits that land on master. + if: branch = master and type = push # Travis VMs are 64-bit but we compile both for 32 and 64 bit. To enable the # 32-bit builds to work, we need gcc-multilib. diff --git a/util/deploy_docs_from_travis.sh b/util/deploy_docs_from_travis.sh index 1cb8d6a1..ae5e5b9c 100644 --- a/util/deploy_docs_from_travis.sh +++ b/util/deploy_docs_from_travis.sh @@ -1,27 +1,34 @@ #!/bin/bash - set -e +# Install the Wren Pygments lexer. +cd util/pygments-lexer +python setup.py develop +cd ../.. + +# Build the docs. make gh-pages -# TODO: This strips the syntax highlighting because the custom pygments lexer -# isn't installed. -git clone https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} gh-pages-repo +# Clone the repo at the gh-pages branch. +# TODO: Testing right now, so not using live branch. +git clone https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} gh-pages-repo \ + --branch gh-pages-temp --depth 1 cd gh-pages-repo -git checkout gh-pages +# Copy them into the gh-pages branch. rm -rf * cp -r ../build/gh-pages/* . -# TODO: Restore CNAME file that gets deleted by `rm -rf *`. +# Restore CNAME file that gets deleted by `rm -rf *`. +echo "wren.io" > "CNAME" git status ls if ! $( git diff-index --quiet HEAD ) ; then - git config user.name "Travis CI" - git config user.email "$COMMIT_AUTHOR_EMAIL" - git add --all . - git commit -m "Deploy to GitHub Pages: ${SHA}" - git push + git config user.name "Travis CI" + git config user.email "$COMMIT_AUTHOR_EMAIL" + git add --all . + git commit -m "Deploy to GitHub Pages: ${SHA}" + git push fi From db77a9dce4c4a45af118734774f1658c7ae1a652 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 11:05:41 -0700 Subject: [PATCH 34/44] Disable container-based build so we can use sudo. --- .travis.yml | 8 +++++++- util/deploy_docs_from_travis.sh | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0306bff0..4238a719 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,5 +27,11 @@ addons: - python3-markdown - ruby-sass -sudo: false # Enable container-based builds. +# Can't do container-based builds for now because installing the custom +# Pygments lexer to generate the docs requires sudo. :( If that changes, +# uncomment the next line and delete the "sudo" and "dist" lines. +# sudo: false # Enable container-based builds. +sudo: required +dist: trusty + script: make WREN_CFLAGS=${WREN_OPTIONS} ${CI_ARCHS} diff --git a/util/deploy_docs_from_travis.sh b/util/deploy_docs_from_travis.sh index ae5e5b9c..f1fffdb8 100644 --- a/util/deploy_docs_from_travis.sh +++ b/util/deploy_docs_from_travis.sh @@ -3,7 +3,7 @@ set -e # Install the Wren Pygments lexer. cd util/pygments-lexer -python setup.py develop +python sudo setup.py develop cd ../.. # Build the docs. From d13cafed710cda2263e77fd7535441c55cef54c1 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 11:17:36 -0700 Subject: [PATCH 35/44] Fix command. I am an idiot. --- util/deploy_docs_from_travis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/deploy_docs_from_travis.sh b/util/deploy_docs_from_travis.sh index f1fffdb8..fb72b683 100644 --- a/util/deploy_docs_from_travis.sh +++ b/util/deploy_docs_from_travis.sh @@ -3,7 +3,7 @@ set -e # Install the Wren Pygments lexer. cd util/pygments-lexer -python sudo setup.py develop +sudo python setup.py develop cd ../.. # Build the docs. From 52f08aec058527d1132b4655f19fe4468ae58206 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 11:44:53 -0700 Subject: [PATCH 36/44] Install the custom pygments lexer using python3. --- doc/site/contributing.markdown | 4 ++-- util/deploy_docs_from_travis.sh | 2 +- util/pygments-lexer/Wren.egg-info/PKG-INFO | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/site/contributing.markdown b/doc/site/contributing.markdown index fa36aae1..e7ad0d17 100644 --- a/doc/site/contributing.markdown +++ b/doc/site/contributing.markdown @@ -66,7 +66,7 @@ for Wren. To install that, run: :::sh $ cd util/pygments-lexer - $ sudo python setup.py develop + $ sudo python3 setup.py develop $ cd ../.. # Back to the root Wren directory. Now you can build the docs: @@ -79,7 +79,7 @@ server from there. Python includes one: :::sh $ cd build/docs - $ python -m SimpleHTTPServer + $ python3 -m http.server Running `make docs` is a drag every time you change a line of Markdown or SASS, so there is also a file watching version that will automatically regenerate the diff --git a/util/deploy_docs_from_travis.sh b/util/deploy_docs_from_travis.sh index fb72b683..6baf5522 100644 --- a/util/deploy_docs_from_travis.sh +++ b/util/deploy_docs_from_travis.sh @@ -3,7 +3,7 @@ set -e # Install the Wren Pygments lexer. cd util/pygments-lexer -sudo python setup.py develop +sudo python3 setup.py develop cd ../.. # Build the docs. diff --git a/util/pygments-lexer/Wren.egg-info/PKG-INFO b/util/pygments-lexer/Wren.egg-info/PKG-INFO index b6a8e409..b583a1da 100644 --- a/util/pygments-lexer/Wren.egg-info/PKG-INFO +++ b/util/pygments-lexer/Wren.egg-info/PKG-INFO @@ -8,5 +8,6 @@ Home-page: UNKNOWN Author: Robert Nystrom Author-email: UNKNOWN License: UNKNOWN +Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: UNKNOWN From 7d8292fe90c8569060ac3bdcaeb0050a8585438b Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 11:57:11 -0700 Subject: [PATCH 37/44] Fix a Travis dependency. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4238a719..61c1efa0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,9 @@ addons: packages: - gcc-multilib - g++-multilib + # These are needed for building and deploying the docs. - python3-markdown + - python3-setuptools - ruby-sass # Can't do container-based builds for now because installing the custom From 09be27d3d50915e11a451cd7829250d60169d9ba Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 12:12:07 -0700 Subject: [PATCH 38/44] Install pygments on Travis. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 61c1efa0..dab7dee3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ addons: - g++-multilib # These are needed for building and deploying the docs. - python3-markdown + - python3-pygments - python3-setuptools - ruby-sass From c7c69d12915b4f0cb3fb86e688011fd5ca126010 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 12:12:37 -0700 Subject: [PATCH 39/44] Check in shared Xcode file. --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 util/xcode/wren.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/util/xcode/wren.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/util/xcode/wren.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/util/xcode/wren.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + From 3920818b46314a8dbb3ec9dbb1c82e15888a65b2 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 20:44:22 -0700 Subject: [PATCH 40/44] Install smartypants on Travis. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index dab7dee3..bd24d4ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ addons: - python3-markdown - python3-pygments - python3-setuptools + - python3-smartypants - ruby-sass # Can't do container-based builds for now because installing the custom From 361b57fa8d235fabca94ed8a9a1cf670bd696d32 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 20:51:48 -0700 Subject: [PATCH 41/44] Try to get right package name for smartypants. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bd24d4ac..904f8363 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,8 +28,8 @@ addons: - python3-markdown - python3-pygments - python3-setuptools - - python3-smartypants - ruby-sass + - smartypants # Can't do container-based builds for now because installing the custom # Pygments lexer to generate the docs requires sudo. :( If that changes, From 7b42a9d62193c12873f9d3487c377a1bbce727ac Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 21:00:59 -0700 Subject: [PATCH 42/44] More guessing at package names. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 904f8363..5cacbbc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,8 +28,8 @@ addons: - python3-markdown - python3-pygments - python3-setuptools + - python-smartypants - ruby-sass - - smartypants # Can't do container-based builds for now because installing the custom # Pygments lexer to generate the docs requires sudo. :( If that changes, From ef5f38b48f252f3dd472deca4532e978e0fb2ff5 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sat, 14 Jul 2018 21:35:33 -0700 Subject: [PATCH 43/44] Turn on auto-deploying docs. Looks like it's working now. --- .travis.yml | 1 - doc/site/classes.markdown | 7 ++++--- util/deploy_docs_from_travis.sh | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5cacbbc3..dab7dee3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,6 @@ addons: - python3-markdown - python3-pygments - python3-setuptools - - python-smartypants - ruby-sass # Can't do container-based builds for now because installing the custom diff --git a/doc/site/classes.markdown b/doc/site/classes.markdown index 2b5659fe..e739f98f 100644 --- a/doc/site/classes.markdown +++ b/doc/site/classes.markdown @@ -158,9 +158,10 @@ are available on an object. When you write: unicorn.isFancy You're saying "look up the method `isFancy` in the scope of the object -`unicorn`". In this case, the fact that you want to look up a *method* `isFancy` -and not a *variable* `isFancy` is explicit. That's what `.` does and the -object to the left of the period is the object you want to look up the method on. +`unicorn`”. In this case, the fact that you want to look up a *method* +`isFancy` and not a *variable* `isFancy` is explicit. That's what `.` does and +the object to the left of the period is the object you want to look up the +method on. ### `this` diff --git a/util/deploy_docs_from_travis.sh b/util/deploy_docs_from_travis.sh index 6baf5522..7886cc7b 100644 --- a/util/deploy_docs_from_travis.sh +++ b/util/deploy_docs_from_travis.sh @@ -10,9 +10,8 @@ cd ../.. make gh-pages # Clone the repo at the gh-pages branch. -# TODO: Testing right now, so not using live branch. git clone https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} gh-pages-repo \ - --branch gh-pages-temp --depth 1 + --branch gh-pages --depth 1 cd gh-pages-repo # Copy them into the gh-pages branch. From 09f4beff4aa572fb5b2e6e035fbfdad501584581 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 15 Jul 2018 10:48:56 -0700 Subject: [PATCH 44/44] Add trim methods on String: - trim() - trim(chars) - trimEnd() - trimEnd(chars) - trimStart() - trimStart(chars) --- doc/site/modules/core/string.markdown | 86 ++++++++++++++----- src/vm/wren_core.wren | 42 +++++++++ src/vm/wren_core.wren.inc | 50 +++++++++++ test/core/string/trim.wren | 16 ++++ test/core/string/trim_chars_not_string.wren | 1 + test/core/string/trim_end.wren | 16 ++++ .../string/trim_end_chars_not_string.wren | 1 + test/core/string/trim_start.wren | 16 ++++ .../string/trim_start_chars_not_string.wren | 1 + 9 files changed, 209 insertions(+), 20 deletions(-) create mode 100644 test/core/string/trim.wren create mode 100644 test/core/string/trim_chars_not_string.wren create mode 100644 test/core/string/trim_end.wren create mode 100644 test/core/string/trim_end_chars_not_string.wren create mode 100644 test/core/string/trim_start.wren create mode 100644 test/core/string/trim_start_chars_not_string.wren diff --git a/doc/site/modules/core/string.markdown b/doc/site/modules/core/string.markdown index 8d28cb4e..b9b459e6 100644 --- a/doc/site/modules/core/string.markdown +++ b/doc/site/modules/core/string.markdown @@ -129,28 +129,12 @@ negative to count backwards from the end of the string. It is a runtime error if `search` is not a string or `start` is not an integer index within the string's byte length. -### **split**(separator) - -Returns a list of one or more strings separated by `separator`. - - :::wren - var string = "abc abc abc" - System.print(string.split(" ")) //> [abc, abc, abc] - -It is a runtime error if `separator` is not a string or is an empty string. - -### **replace**(old, swap) - -Returns a new string with all occurences of `old` replaced with `swap`. - - :::wren - var string = "abc abc abc" - System.print(string.replace(" ", "")) //> abcabcabc - ### **iterate**(iterator), **iteratorValue**(iterator) -Implements the [iterator protocol](../../control-flow.html#the-iterator-protocol) -for iterating over the *code points* in the string: +Implements the [iterator protocol][] for iterating over the *code points* in the +string: + +[iterator protocol]: ../../control-flow.html#the-iterator-protocol :::wren var codePoints = [] @@ -163,12 +147,74 @@ for iterating over the *code points* in the string: If the string contains any bytes that are not valid UTF-8, this iterates over those too, one byte at a time. +### **replace**(old, swap) + +Returns a new string with all occurrences of `old` replaced with `swap`. + + :::wren + var string = "abc abc abc" + System.print(string.replace(" ", "")) //> abcabcabc + +### **split**(separator) + +Returns a list of one or more strings separated by `separator`. + + :::wren + var string = "abc abc abc" + System.print(string.split(" ")) //> [abc, abc, abc] + +It is a runtime error if `separator` is not a string or is an empty string. + ### **startsWith**(prefix) Checks if the string starts with `prefix`. It is a runtime error if `prefix` is not a string. +### **trim**() + +Returns a new string with whitespace removed from the beginning and end of this +string. "Whitespace" is space, tab, carriage return, and line feed characters. + + :::wren + System.print(" \nstuff\r\t".trim()) //> stuff + +### **trim**(chars) + +Returns a new string with all code points in `chars` removed from the beginning +and end of this string. + + :::wren + System.print("ᵔᴥᵔᴥᵔbearᵔᴥᴥᵔᵔ".trim("ᵔᴥ")) //> bear + +### **trimEnd**() + +Like `trim()` but only removes from the end of the string. + + :::wren + System.print(" \nstuff\r\t".trimEnd()) //> " \nstuff" + +### **trimEnd**(chars) + +Like `trim()` but only removes from the end of the string. + + :::wren + System.print("ᵔᴥᵔᴥᵔbearᵔᴥᴥᵔᵔ".trimEnd("ᵔᴥ")) //> ᵔᴥᵔᴥᵔbear + +### **trimStart**() + +Like `trim()` but only removes from the beginning of the string. + + :::wren + System.print(" \nstuff\r\t".trimStart()) //> "stuff\r\t" + +### **trimStart**(chars) + +Like `trim()` but only removes from the beginning of the string. + + :::wren + System.print("ᵔᴥᵔᴥᵔbearᵔᴥᴥᵔᵔ".trimStart("ᵔᴥ")) //> bearᵔᴥᴥᵔᵔ + ### **+**(other) operator Returns a new string that concatenates this string and `other`. diff --git a/src/vm/wren_core.wren b/src/vm/wren_core.wren index 2788308c..c2d0af92 100644 --- a/src/vm/wren_core.wren +++ b/src/vm/wren_core.wren @@ -236,6 +236,48 @@ class String is Sequence { return result } + trim() { trim_("\t\r\n ", true, true) } + trim(chars) { trim_(chars, true, true) } + trimEnd() { trim_("\t\r\n ", false, true) } + trimEnd(chars) { trim_(chars, false, true) } + trimStart() { trim_("\t\r\n ", true, false) } + trimStart(chars) { trim_(chars, true, false) } + + trim_(chars, trimStart, trimEnd) { + if (!(chars is String)) { + Fiber.abort("Characters must be a string.") + } + + var codePoints = chars.codePoints.toList + + var start + if (trimStart) { + while (start = iterate(start)) { + if (!codePoints.contains(codePointAt_(start))) break + } + + if (start == false) return "" + } else { + start = 0 + } + + var end + if (trimEnd) { + end = byteCount_ - 1 + while (end >= start) { + var codePoint = codePointAt_(end) + if (codePoint != -1 && !codePoints.contains(codePoint)) break + end = end - 1 + } + + if (end < start) return "" + } else { + end = -1 + } + + return this[start..end] + } + *(count) { if (!(count is Num) || !count.isInteger || count < 0) { Fiber.abort("Count must be a non-negative integer.") diff --git a/src/vm/wren_core.wren.inc b/src/vm/wren_core.wren.inc index 8251f9f3..63d6648f 100644 --- a/src/vm/wren_core.wren.inc +++ b/src/vm/wren_core.wren.inc @@ -238,6 +238,56 @@ static const char* coreModuleSource = " return result\n" " }\n" "\n" +" trim() { trim_(\"\t\r\n \", true, true) }\n" +"\n" +" trim(chars) { trim_(chars, true, true) }\n" +"\n" +" trimEnd() { trim_(\"\t\r\n \", false, true) }\n" +"\n" +" trimEnd(chars) { trim_(chars, false, true) }\n" +"\n" +" trimStart() { trim_(\"\t\r\n \", true, false) }\n" +"\n" +" trimStart(chars) { trim_(chars, true, false) }\n" +"\n" +" trim_(chars, trimStart, trimEnd) {\n" +" if (!(chars is String)) {\n" +" Fiber.abort(\"Characters must be a string.\")\n" +" }\n" +"\n" +" var codePoints = chars.codePoints.toList\n" +"// System.print(\"code points %(codePoints)\")\n" +"\n" +" var start\n" +" if (trimStart) {\n" +" while (start = iterate(start)) {\n" +" if (!codePoints.contains(codePointAt_(start))) break\n" +" }\n" +"\n" +" if (start == false) return \"\"\n" +" } else {\n" +" start = 0\n" +" }\n" +"\n" +" var end\n" +" if (trimEnd) {\n" +" end = byteCount_ - 1\n" +" while (end >= start) {\n" +" var codePoint = codePointAt_(end)\n" +"// System.print(\"test %(end) : %(codePoint)\")\n" +" if (codePoint != -1 && !codePoints.contains(codePoint)) break\n" +" end = end - 1\n" +" }\n" +"\n" +"// System.print(\"range %(start) %(end)\")\n" +" if (end < start) return \"\"\n" +" } else {\n" +" end = -1\n" +" }\n" +"\n" +" return this[start..end]\n" +" }\n" +"\n" " *(count) {\n" " if (!(count is Num) || !count.isInteger || count < 0) {\n" " Fiber.abort(\"Count must be a non-negative integer.\")\n" diff --git a/test/core/string/trim.wren b/test/core/string/trim.wren new file mode 100644 index 00000000..23c3fc29 --- /dev/null +++ b/test/core/string/trim.wren @@ -0,0 +1,16 @@ +System.print("".trim() == "") // expect: true +System.print("foo".trim() == "foo") // expect: true +System.print(" \t\r\nfoo b\tar \t\r\n".trim() == "foo b\tar") // expect: true +System.print(" \t\r\n \t\r\n".trim() == "") // expect: true +System.print(" \n\n\tsøméஃthîng \n\n\t".trim() == "søméஃthîng") // expect: true + +System.print("".trim("abc") == "") // expect: true +System.print("foo".trim("abc") == "foo") // expect: true +System.print("foo".trim("") == "foo") // expect: true +System.print("cbacbfoobarab".trim("abc") == "foobar") // expect: true +System.print("abcbacba".trim("abc") == "") // expect: true +System.print("søméஃthîngsøméஃ".trim("ஃmésø") == "thîng") // expect: true + +// 8-bit clean. +System.print(" \t\ra\0b \t\r".trim() == "a\0b") // expect: true +System.print("\0a\0b\0c\0".trim("c\0a") == "b") // expect: true diff --git a/test/core/string/trim_chars_not_string.wren b/test/core/string/trim_chars_not_string.wren new file mode 100644 index 00000000..ae68b819 --- /dev/null +++ b/test/core/string/trim_chars_not_string.wren @@ -0,0 +1 @@ +"abracadabra".trim(123) // expect runtime error: Characters must be a string. diff --git a/test/core/string/trim_end.wren b/test/core/string/trim_end.wren new file mode 100644 index 00000000..fc082743 --- /dev/null +++ b/test/core/string/trim_end.wren @@ -0,0 +1,16 @@ +System.print("".trimEnd() == "") // expect: true +System.print("foo".trimEnd() == "foo") // expect: true +System.print(" \t\r\nfoo b\tar \t\r\n".trimEnd() == " \t\r\nfoo b\tar") // expect: true +System.print(" \t\r\n \t\r\n".trimEnd() == "") // expect: true +System.print("søméஃthîng \n\n\t".trimEnd() == "søméஃthîng") // expect: true + +System.print("".trimEnd("abc") == "") // expect: true +System.print("foo".trimEnd("abc") == "foo") // expect: true +System.print("foo".trimEnd("") == "foo") // expect: true +System.print("cbacbfoobarab".trimEnd("abc") == "cbacbfoobar") // expect: true +System.print("abcbacba".trimEnd("abc") == "") // expect: true +System.print("søméஃthîngsøméஃ".trimEnd("ஃmésø") == "søméஃthîng") // expect: true + +// 8-bit clean. +System.print(" \t\ra\0b \t\r".trimEnd() == " \t\ra\0b") // expect: true +System.print("\0a\0b\0c\0".trimEnd("c\0") == "\0a\0b") // expect: true diff --git a/test/core/string/trim_end_chars_not_string.wren b/test/core/string/trim_end_chars_not_string.wren new file mode 100644 index 00000000..929ae08d --- /dev/null +++ b/test/core/string/trim_end_chars_not_string.wren @@ -0,0 +1 @@ +"abracadabra".trimEnd(123) // expect runtime error: Characters must be a string. diff --git a/test/core/string/trim_start.wren b/test/core/string/trim_start.wren new file mode 100644 index 00000000..2e1eb3a4 --- /dev/null +++ b/test/core/string/trim_start.wren @@ -0,0 +1,16 @@ +System.print("".trimStart() == "") // expect: true +System.print("foo".trimStart() == "foo") // expect: true +System.print(" \t\r\nfoo b\tar \t\r\n".trimStart() == "foo b\tar \t\r\n") // expect: true +System.print(" \t\r\n \t\r\n".trimStart() == "") // expect: true +System.print(" \n\n\tsøméஃthîng".trimStart() == "søméஃthîng") // expect: true + +System.print("".trimStart("abc") == "") // expect: true +System.print("foo".trimStart("abc") == "foo") // expect: true +System.print("foo".trimStart("") == "foo") // expect: true +System.print("cbacbfoobarab".trimStart("abc") == "foobarab") // expect: true +System.print("abcbacba".trimStart("abc") == "") // expect: true +System.print("søméஃthîng".trimStart("ஃmésø") == "thîng") // expect: true + +// 8-bit clean. +System.print(" \t\ra\0b".trimStart() == "a\0b") // expect: true +System.print("\0a\0b\0c\0".trimStart("a\0") == "b\0c\0") // expect: true diff --git a/test/core/string/trim_start_chars_not_string.wren b/test/core/string/trim_start_chars_not_string.wren new file mode 100644 index 00000000..8960c7a3 --- /dev/null +++ b/test/core/string/trim_start_chars_not_string.wren @@ -0,0 +1 @@ +"abracadabra".trimStart(123) // expect runtime error: Characters must be a string.