diff --git a/example/hello.wren b/example/hello.wren index 5db92305..26c74f71 100644 --- a/example/hello.wren +++ b/example/hello.wren @@ -6,3 +6,4 @@ class Foo { // line comment var a = Foo.new a.bar "something".contains("meth") +io.write("hey there") diff --git a/runtests b/runtests new file mode 100755 index 00000000..bdcbcf91 --- /dev/null +++ b/runtests @@ -0,0 +1,187 @@ +#!/usr/bin/python + +from collections import defaultdict +from os import listdir +from os.path import abspath, dirname, isdir, isfile, join, realpath, relpath, splitext +import re +from subprocess import Popen, PIPE +import sys + +# Runs the tests. +WREN_DIR = dirname(realpath(__file__)) +TEST_DIR = join(WREN_DIR, 'test') + +if sys.platform == 'win32': + WREN_APP = join(WREN_DIR, 'Debug', 'wren.exe') + if not isfile(WREN_DIR): + WREN_APP = join(WREN_DIR, 'Release', 'wren.exe') + if not isfile(WREN_APP): + sys.exit('Cannot find wren.exe!') +elif sys.platform.startswith('linux'): + WREN_APP = join(WREN_DIR, '1', 'out', 'Debug', 'wren') + if not isfile(WREN_APP): + WREN_APP = join(WREN_DIR, '1', 'out', 'Release', 'wren') + if not isfile(WREN_APP): + sys.exit('Cannot find wren!') +elif sys.platform.startswith('darwin'): + WREN_APP = join(WREN_DIR, 'build', 'Debug', 'wren') + if not isfile(WREN_APP): + WREN_APP = join(WREN_DIR, 'build', 'Release', 'wren') + if not isfile(WREN_APP): + sys.exit('Cannot find wren!') +else: + sys.exit('System not supported!') + +EXPECT_PATTERN = re.compile(r'// expect: (.*)') +SKIP_PATTERN = re.compile(r'// skip: (.*)') +NONTEST_PATTERN = re.compile(r'// nontest') + +if sys.platform == 'win32': + class color: + GREEN = '' + RED = '' + DEFAULT = '' + PINK = '' + YELLOW = '' +else: + class color: + GREEN = '\033[32m' + RED = '\033[31m' + DEFAULT = '\033[0m' + PINK = '\033[91m' + YELLOW = '\033[33m' + +passed = 0 +failed = 0 +skipped = defaultdict(int) +num_skipped = 0; + + +def walk(dir, callback): + """ Walks [dir], and executes [callback] on each file. """ + dir = abspath(dir) + for file in [file for file in listdir(dir) if not file in [".",".."]]: + nfile = join(dir, file) + if isdir(nfile): + walk(nfile, callback) + else: + callback(nfile) + + +def print_line(line=None): + # Erase the line. + print '\033[2K', + # Move the cursor to the beginning. + print '\r', + if line: + print line, + sys.stdout.flush() + + +def run_test(path): + global passed + global failed + global skipped + global num_skipped + + if (splitext(path)[1] != '.wren'): + return + + # Check if we are just running a subset of the tests. + if len(sys.argv) == 2: + this_test = relpath(path, join(WREN_DIR, 'test')) + if not this_test.startswith(sys.argv[1]): + return + + # Make a nice short path relative to the working directory. + path = relpath(path) + + # Read the test and parse out the expectations. + expect_output = [] + expect_error = [] + expect_return = 0 + + print_line('Passed: ' + color.GREEN + str(passed) + color.DEFAULT + + ' Failed: ' + color.RED + str(failed) + color.DEFAULT + + ' Skipped: ' + color.YELLOW + str(num_skipped) + color.DEFAULT) + + line_num = 1 + with open(path, 'r') as file: + for line in file: + match = EXPECT_PATTERN.search(line) + if match: + expect_output.append((match.group(1), line_num)) + + match = SKIP_PATTERN.search(line) + if match: + num_skipped += 1 + skipped[match.group(1)] += 1 + return + + match = NONTEST_PATTERN.search(line) + if match: + # Not a test file at all, so ignore it. + return + + line_num += 1 + + # Invoke wren and run the test. + proc = Popen([WREN_APP, path], stdout=PIPE, stderr=PIPE) + (out, err) = proc.communicate() + (out, err) = out.replace('\r\n', '\n'), err.replace('\r\n', '\n') + + fails = [] + + # Validate the exit code. + if proc.returncode != 0: + fails.append('Expected return code 0 and got {0}.'. + format(proc.returncode)) + else: + # Validate the output. + expect_index = 0 + + # Remove the trailing last empty line. + out_lines = out.split('\n') + if out_lines[-1] == '': + del out_lines[-1] + + for line in out_lines: + if expect_index >= len(expect_output): + fails.append('Got output "{0}" when none was expected.'. + format(line)) + elif expect_output[expect_index][0] != line: + fails.append('Expected output "{0}" on line {1} and got "{2}".'. + format(expect_output[expect_index][0], + expect_output[expect_index][1], line)) + expect_index += 1 + + while expect_index < len(expect_output): + fails.append('Missing expected output "{0}" on line {1}.'. + format(expect_output[expect_index][0], + expect_output[expect_index][1])) + expect_index += 1 + + # Display the results. + if len(fails) == 0: + passed += 1 + print color.GREEN + 'PASS' + color.DEFAULT + ': ' + path + else: + failed += 1 + print_line(color.RED + 'FAIL' + color.DEFAULT + ': ' + path) + print + for fail in fails: + print ' ', color.PINK + fail + color.DEFAULT + print + +walk(TEST_DIR, run_test) + +print_line() +if failed == 0: + print 'All ' + color.GREEN + str(passed) + color.DEFAULT + ' tests passed.' +else: + print (color.GREEN + str(passed) + color.DEFAULT + ' tests passed. ' + + color.RED + str(failed) + color.DEFAULT + ' tests failed.') + +for key in sorted(skipped.keys()): + print ('Skipped ' + color.YELLOW + str(skipped[key]) + color.DEFAULT + + ' tests: ' + key) diff --git a/src/compiler.c b/src/compiler.c index 7c912da4..e1159ce0 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -268,8 +268,8 @@ int defineName(Compiler* compiler) } int symbol = addSymbol(symbols, - compiler->parser->source + compiler->parser->previous.start, - compiler->parser->previous.end - compiler->parser->previous.start); + compiler->parser->source + compiler->parser->previous.start, + compiler->parser->previous.end - compiler->parser->previous.start); if (symbol == -1) { diff --git a/src/main.c b/src/main.c index 54296cc7..64817436 100644 --- a/src/main.c +++ b/src/main.c @@ -30,9 +30,7 @@ int main(int argc, const char * argv[]) if (block) { - Value value = interpret(vm, block); - printValue(value); - printf("\n"); + interpret(vm, block); } freeVM(vm); diff --git a/src/primitives.c b/src/primitives.c index 39ebb88f..e5d9a33a 100644 --- a/src/primitives.c +++ b/src/primitives.c @@ -1,3 +1,4 @@ +#include #include #include @@ -6,14 +7,21 @@ #define PRIMITIVE(cls, name, prim) \ { \ int symbol = ensureSymbol(&vm->symbols, name, strlen(name)); \ - vm->cls##Class->methods[symbol].type = METHOD_PRIMITIVE; \ - vm->cls##Class->methods[symbol].primitive = primitive_##cls##_##prim; \ + cls->methods[symbol].type = METHOD_PRIMITIVE; \ + cls->methods[symbol].primitive = primitive_##prim; \ } -#define DEF_PRIMITIVE(cls, prim) \ - static Value primitive_##cls##_##prim(Value* args, int numArgs) +#define DEF_PRIMITIVE(prim) \ + static Value primitive_##prim(Value* args, int numArgs) -DEF_PRIMITIVE(num, abs) +#define GLOBAL(cls, name) \ + { \ + ObjInstance* obj = makeInstance(cls); \ + int symbol = addSymbol(&vm->globalSymbols, name, strlen(name)); \ + vm->globals[symbol] = (Value)obj; \ + } + +DEF_PRIMITIVE(num_abs) { double value = ((ObjNum*)args[0])->value; if (value < 0) value = -value; @@ -21,7 +29,7 @@ DEF_PRIMITIVE(num, abs) return (Value)makeNum(value); } -DEF_PRIMITIVE(string, contains) +DEF_PRIMITIVE(string_contains) { const char* string = ((ObjString*)args[0])->value; // TODO(bob): Check type of arg first! @@ -31,16 +39,27 @@ DEF_PRIMITIVE(string, contains) return (Value)makeNum(strstr(string, search) != NULL); } -DEF_PRIMITIVE(string, count) +DEF_PRIMITIVE(string_count) { double count = strlen(((ObjString*)args[0])->value); return (Value)makeNum(count); } +DEF_PRIMITIVE(io_write) +{ + printValue(args[1]); + printf("\n"); + return args[1]; +} + void registerPrimitives(VM* vm) { - PRIMITIVE(num, "abs", abs); - PRIMITIVE(string, "contains ", contains); - PRIMITIVE(string, "count", count); + PRIMITIVE(vm->numClass, "abs", num_abs); + PRIMITIVE(vm->stringClass, "contains ", string_contains); + PRIMITIVE(vm->stringClass, "count", string_count); + + ObjClass* ioClass = makeClass(); + PRIMITIVE(ioClass, "write ", io_write); + GLOBAL(ioClass, "io"); } \ No newline at end of file diff --git a/src/vm.c b/src/vm.c index 9829a54c..7d2d2faa 100644 --- a/src/vm.c +++ b/src/vm.c @@ -200,9 +200,6 @@ Value interpret(VM* vm, ObjBlock* block) int constant = frame->block->bytecode[frame->ip++]; Value value = frame->block->constants[constant]; fiber.stack[fiber.stackSize++] = value; - printf("load constant "); - printValue(value); - printf(" to %d\n", fiber.stackSize - 1); break; } @@ -213,13 +210,11 @@ Value interpret(VM* vm, ObjBlock* block) // Define a "new" method on the metaclass. // TODO(bob): Can this be inherited? int newSymbol = ensureSymbol(&vm->symbols, "new", strlen("new")); - printf("define new %d\n", newSymbol); classObj->metaclass->methods[newSymbol].type = METHOD_PRIMITIVE; classObj->metaclass->methods[newSymbol].primitive = primitive_metaclass_new; push(&fiber, (Value)classObj); - printf("push class at %d\n", fiber.stackSize - 1); break; } @@ -232,10 +227,6 @@ Value interpret(VM* vm, ObjBlock* block) ObjBlock* body = (ObjBlock*)frame->block->constants[constant]; classObj->methods[symbol].type = METHOD_BLOCK; classObj->methods[symbol].block = body; - - printf("define method %d using constant %d on ", symbol, constant); - printValue((Value)classObj); - printf("\n"); break; } @@ -243,14 +234,12 @@ Value interpret(VM* vm, ObjBlock* block) { int local = frame->block->bytecode[frame->ip++]; push(&fiber, fiber.stack[frame->locals + local]); - printf("load local %d to %d\n", local, fiber.stackSize - 1); break; } case CODE_STORE_LOCAL: { int local = frame->block->bytecode[frame->ip++]; - printf("store local %d from %d\n", local, fiber.stackSize - 1); fiber.stack[frame->locals + local] = fiber.stack[fiber.stackSize - 1]; break; } @@ -259,25 +248,21 @@ Value interpret(VM* vm, ObjBlock* block) { int global = frame->block->bytecode[frame->ip++]; push(&fiber, vm->globals[global]); - printf("load global %d to %d\n", global, fiber.stackSize - 1); break; } case CODE_STORE_GLOBAL: { int global = frame->block->bytecode[frame->ip++]; - printf("store global %d from %d\n", global, fiber.stackSize - 1); vm->globals[global] = fiber.stack[fiber.stackSize - 1]; break; } case CODE_DUP: push(&fiber, fiber.stack[fiber.stackSize - 1]); - printf("dup %d\n", fiber.stackSize - 1); break; case CODE_POP: - printf("pop %d\n", fiber.stackSize - 1); pop(&fiber); break; @@ -323,10 +308,6 @@ Value interpret(VM* vm, ObjBlock* block) break; } - printf("call %d on ", symbol); - printValue(receiver); - printf("\n"); - Method* method = &classObj->methods[symbol]; switch (method->type) { @@ -363,19 +344,10 @@ Value interpret(VM* vm, ObjBlock* block) fiber.numFrames--; // If we are returning from the top-level block, just return the value. - if (fiber.numFrames == 0) - { - printf("done with result "); - printValue(result); - printf("\n"); - return result; - } + if (fiber.numFrames == 0) return result; // Store the result of the block in the first slot, which is where the // caller expects it. - printf("return and store result "); - printValue(result); - printf(" in %d\n", frame->locals); fiber.stack[frame->locals] = result; // Discard the stack slots for the locals. @@ -391,7 +363,7 @@ void printValue(Value value) switch (value->type) { case OBJ_NUM: - printf("%f", ((ObjNum*)value)->value); + printf("%g", ((ObjNum*)value)->value); break; case OBJ_STRING: diff --git a/test/number_literals.wren b/test/number_literals.wren new file mode 100644 index 00000000..28fd3475 --- /dev/null +++ b/test/number_literals.wren @@ -0,0 +1,6 @@ +io.write(123) // expect: 123 +io.write(987654) // expect: 987654 +io.write(0) // expect: 0 +io.write(-0) // expect: 0 + +// TODO(bob): Floating point numbers.