diff --git a/.travis.yml b/.travis.yml index 938b01f5..dab7dee3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,22 @@ 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" + +# 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. addons: @@ -15,5 +24,17 @@ addons: packages: - gcc-multilib - g++-multilib -sudo: false # Enable container-based builds. + # These are needed for building and deploying the docs. + - python3-markdown + - python3-pygments + - python3-setuptools + - ruby-sass + +# 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/AUTHORS b/AUTHORS index 23d317f3..4b29cd09 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,3 +20,6 @@ Damien Radtke Max Ferguson Sven Bergström Kyle Charters +Marshall Bowers +Michal Kozakiewicz +Charlotte Koch 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. diff --git a/Makefile b/Makefile index 68b1d8f6..0b01324d 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,7 @@ unit_test: # Generate the Wren site. docs: + mkdir -p build $(V) ./util/generate_docs.py # Continuously generate and serve the Wren site. diff --git a/doc/site/classes.markdown b/doc/site/classes.markdown index e436d3e6..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` @@ -430,7 +431,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 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/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(_,_). } 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, 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: 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/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) diff --git a/src/cli/modules.c b/src/cli/modules.c index 47b6286d..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]. // @@ -241,7 +242,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/cli/stat.h b/src/cli/stat.h index f54e6b6f..b333bce5 100644 --- a/src/cli/stat.h +++ b/src/cli/stat.h @@ -9,11 +9,21 @@ #include // Map to Windows permission flags. + #ifndef S_IRUSR #define S_IRUSR _S_IREAD - #define S_IWUSR _S_IWRITE + #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 6a471458..eec22036 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 } @@ -78,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 @@ -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..7ef78c5a 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" @@ -80,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" @@ -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" diff --git a/src/optional/wren_opt_meta.c b/src/optional/wren_opt_meta.c index 56b8fbff..d161cf28 100644 --- a/src/optional/wren_opt_meta.c +++ b/src/optional/wren_opt_meta.c @@ -61,9 +61,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 e7de5038..adb261f6 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 { @@ -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); } @@ -1700,7 +1700,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)++] = '_'; @@ -1863,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 }; @@ -2727,7 +2731,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: @@ -2757,7 +2760,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: @@ -2777,6 +2780,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: @@ -3077,7 +3081,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 @@ -3163,7 +3167,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); @@ -3416,6 +3420,9 @@ void definition(Compiler* compiler) ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, bool isExpression, bool printErrors) { + // 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; @@ -3443,14 +3450,15 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, nextToken(&parser); int numExistingVariables = module->variables.count; - + Compiler compiler; - initCompiler(&compiler, &parser, NULL, true); + initCompiler(&compiler, &parser, NULL, false); ignoreNewlines(&compiler); if (isExpression) { expression(&compiler); + consume(&compiler, TOKEN_EOF, "Expect end of expression."); } else { @@ -3458,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."); @@ -3480,8 +3488,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."); } @@ -3495,7 +3503,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 +3513,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 +3534,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 +3543,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 +3553,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); } } @@ -3567,6 +3570,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_core.c b/src/vm/wren_core.c index 0debbe51..56b6222c 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_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/src/vm/wren_debug.c b/src/vm/wren_debug.c index 692839fd..d0b8b743 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..0989fefe 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); + return symbols->count - 1; } @@ -46,20 +40,30 @@ 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 (wrenStringEqualsCString(symbols->data[i], name, length)) 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..4e43524d 100644 --- a/src/vm/wren_utils.h +++ b/src/vm/wren_utils.h @@ -6,12 +6,9 @@ // 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; +// 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 @@ -68,7 +65,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 +87,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..fc645359 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) @@ -1137,11 +1141,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) @@ -1296,9 +1301,8 @@ bool wrenValuesEqual(Value a, Value b) { 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 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 ffb79f08..31f19f50 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" @@ -102,7 +103,8 @@ typedef enum { 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 +114,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 +147,7 @@ typedef struct DECLARE_BUFFER(Value, Value); // A heap-allocated string object. -typedef struct +struct sObjString { Obj obj; @@ -157,7 +159,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 +173,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 +typedef struct sObjUpvalue { // The object header. Note that upvalues have this because they are garbage // collected, but they are not first class Wren objects. @@ -187,7 +189,7 @@ 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; + struct sObjUpvalue* next; } ObjUpvalue; // The type of a primitive function. @@ -628,10 +630,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, @@ -733,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); diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index dc016f00..cdeb869f 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -151,6 +151,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); @@ -421,7 +424,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 @@ -493,8 +496,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]); } } @@ -730,7 +733,9 @@ static Value importModule(WrenVM* vm, Value name) // If the module is already loaded, we don't need to do anything. Value existing = wrenMapGet(vm->modules, name); if (!IS_UNDEFINED(existing)) return existing; - + + wrenPushRoot(vm, AS_OBJ(name)); + const char* source = NULL; bool allocatedSource = true; @@ -759,6 +764,7 @@ static Value importModule(WrenVM* vm, Value name) if (source == NULL) { vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name); + wrenPopRoot(vm); // name. return NULL_VAL; } @@ -773,9 +779,12 @@ static Value importModule(WrenVM* vm, Value name) { vm->fiber->error = wrenStringFormat(vm, "Could not compile module '@'.", name); + wrenPopRoot(vm); // name. return NULL_VAL; } - + + wrenPopRoot(vm); // name. + // Return the closure that executes the module. return OBJ_VAL(moduleClosure); } @@ -1306,20 +1315,18 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) CASE_CODE(IMPORT_MODULE): { - Value name = fn->constants.data[READ_SHORT()]; - - Value result = importModule(vm, name); + // 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. Store the + // imported module's closure in the slot in case a GC happens when + // invoking the closure. + PUSH(importModule(vm, fn->constants.data[READ_SHORT()])); if (!IS_NULL(fiber->error)) RUNTIME_ERROR(); - - // Make a slot on the stack for the module's closure to place the return - // value. It will be popped after the module body code returns. - PUSH(NULL_VAL); // 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(); } @@ -1327,7 +1334,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) { // The module has already been loaded. Remember it so we can import // variables from it if needed. - vm->lastModule = AS_MODULE(result); + vm->lastModule = AS_MODULE(PEEK()); } DISPATCH(); @@ -1477,7 +1484,7 @@ WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, wrenPushRoot(vm, (Obj*)closure); ObjFiber* fiber = wrenNewFiber(vm, closure); wrenPopRoot(vm); // closure. - + return runInterpreter(vm, fiber); } @@ -1774,7 +1781,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); 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. diff --git a/test/language/bom.wren b/test/language/bom.wren new file mode 100644 index 00000000..6eddf921 --- /dev/null +++ b/test/language/bom.wren @@ -0,0 +1,2 @@ +// This file should have a UTF-8 byte order mark +System.print("ok") // expect: ok 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 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 diff --git a/util/deploy_docs_from_travis.sh b/util/deploy_docs_from_travis.sh new file mode 100644 index 00000000..7886cc7b --- /dev/null +++ b/util/deploy_docs_from_travis.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -e + +# Install the Wren Pygments lexer. +cd util/pygments-lexer +sudo python3 setup.py develop +cd ../.. + +# Build the docs. +make gh-pages + +# Clone the repo at the gh-pages branch. +git clone https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} gh-pages-repo \ + --branch gh-pages --depth 1 +cd gh-pages-repo + +# Copy them into the gh-pages branch. +rm -rf * +cp -r ../build/gh-pages/* . + +# 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 +fi 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 diff --git a/util/wren.mk b/util/wren.mk index d8273276..c9eefcae 100644 --- a/util/wren.mk +++ b/util/wren.mk @@ -110,7 +110,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. 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 + + +