From f132f596492012624f6b20c96c14a1f68258f719 Mon Sep 17 00:00:00 2001 From: Luchs Date: Sat, 25 Apr 2015 18:10:17 +0200 Subject: [PATCH 1/9] Add syntax example file --- example/syntax.wren | 158 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 example/syntax.wren diff --git a/example/syntax.wren b/example/syntax.wren new file mode 100644 index 00000000..423d5325 --- /dev/null +++ b/example/syntax.wren @@ -0,0 +1,158 @@ +// This file provides examples of syntactic constructs in wren, which is mainly +// interesting for testing syntax highlighters. + +// This is a comment. +/* This is /* a nested */ comment. */ + +// Class definition with a toplevel name. +class SyntaxExample { + // Constructor + new { + // toplevel name `IO` + IO.print("I am a constructor!") + // method calls + variables + fields() + // static method call + SyntaxExample.fields(1) + } + + // Constructor with arguments + new(a, b) { + print(a, b) + field = a + } + + // Method without arguments + variables { + // Valid local variable names. + var hi + var camelCase + var PascalCase + var abc123 + var ALL_CAPS + } + + // Method with empty argument list + fields() { + // Fields + _under_score = 1 + _field = 2 + } + + // Static method with single argument + static fields(a) { + // Static field + __a = a + } + + // setter + field=(value) { _field = value } + + // Method with arguments + print(a, b) { IO.print(a + b) } +} + +// `class`, `is` +class ReservedWords is SyntaxExample { + // `new` + new { + // `super`, `true`, `false` + super(true, false) + // `this` + this.foo + // `new` + new SyntaxExample + } + + foo { + // `var` + var n = 27 + // `while`, `if`, `else` + while (n != 1) if (n % 2 == 0) n = n / 2 else n = 3 * n + 1 + + // `for`, `in` + for (beatle in ["george", "john", "paul", "ringo"]) { + IO.print(beatle) + // `break` + break + } + + // `return`, `null` + return null + } + + imports { + // `import` + import "hello" + // `import`, `for` + import "set" for Set + } + + // `foreign`, `static` + foreign static bar + foreign baz(string) + // (Remove lines above to make this file compile) +} + +class literals is SyntaxExample { + booleans { true || false } + numbers { + 0 + 1234 + -5678 + 3.14159 + 1.0 + -12.34 + 0xdeadbeef + 0x1234567890ABCDEF + } + strings { + "hi there" + // Escapes: + "\0" // The NUL byte: 0. + "\"" // A double quote character. + "\\" // A backslash. + "\a" // Alarm beep. (Who uses this?) + "\b" // Backspace. + "\f" // Formfeed. + "\n" // Newline. + "\r" // Carriage return. + "\t" // Tab. + "\v" // Vertical tab. + // Unicode code points + IO.print("\u0041\u0b83\u00DE") // "AஃÞ" + // Unencoded bytes + IO.print("\x48\x69\x2e") // "Hi." + } + ranges { + 3..8 // inclusive + 4...6 // half-inclusive + } + nothing { null } + + lists { + var list = [1, "banana", true] + list[0] = 5 + list[1..2] + } + maps { + var stringMap = { + "George": "Harrison", + "John": "Lennon", + "Paul": "McCartney", + "Ringo": "Starr" + } + var a = 1 + var weirdMap = { + true: 1, + false: 0, + null: -1, + "str": "abc", + (1..5): 10, + a: 2, + _a: 3, + __a: 4 + } + } +} From e861b865632b43a0ac071a666a680e4c6092e6b5 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Tue, 19 May 2015 06:50:17 -0700 Subject: [PATCH 2/9] Revert 40897f3348ef3df05eb0f50c712ae9b0e3e22ae2. It leaks memory in the case of runtime errors. --- builtin/core.wren | 48 +++++++---------------------------- src/vm/wren_core.c | 48 +++++++---------------------------- test/core/list/to_string.wren | 35 +------------------------ test/core/map/to_string.wren | 26 +------------------ 4 files changed, 20 insertions(+), 137 deletions(-) diff --git a/builtin/core.wren b/builtin/core.wren index e9d0aa92..3867bda9 100644 --- a/builtin/core.wren +++ b/builtin/core.wren @@ -127,34 +127,6 @@ class WhereSequence is Sequence { } class String is Sequence { - // Avoids recursively calling [toString] on [object] and overflowing the - // stack. - // - // If we are already within a call to [safeToString_] on [object] in this - // fiber, then this returns "...". Otherwise, it returns the result of - // calling [ifUnseen]. - static safeToString_(object, ifUnseen) { - if (__seenByFiber == null) __seenByFiber = new Map - var seen = __seenByFiber[Fiber.current] - if (seen == null) { - __seenByFiber[Fiber.current] = seen = new List - } - - // See if we are recursing on it. - for (outer in seen) { - if (Object.same(outer, object)) return "..." - } - - seen.add(object) - - var result = ifUnseen.call() - - seen.removeAt(-1) - if (seen.count == 0) __seenByFiber.remove(Fiber.current) - - return result - } - bytes { new StringByteSequence(this) } } @@ -176,7 +148,7 @@ class List is Sequence { return other } - toString { String.safeToString_(this) { "[" + join(", ") + "]" } } + toString { "[" + join(", ") + "]" } +(other) { var result = this[0..-1] @@ -192,18 +164,16 @@ class Map { values { new MapValueSequence(this) } toString { - return String.safeToString_(this) { - var first = true - var result = "{" + var first = true + var result = "{" - for (key in keys) { - if (!first) result = result + ", " - first = false - result = result + key.toString + ": " + this[key].toString - } - - return result + "}" + for (key in keys) { + if (!first) result = result + ", " + first = false + result = result + key.toString + ": " + this[key].toString } + + return result + "}" } } diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index 1824ed7b..05da56dc 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -141,34 +141,6 @@ static const char* coreLibSource = "}\n" "\n" "class String is Sequence {\n" -" // Avoids recursively calling [toString] on [object] and overflowing the\n" -" // stack.\n" -" //\n" -" // If we are already within a call to [safeToString_] on [object] in this\n" -" // fiber, then this returns \"...\". Otherwise, it returns the result of\n" -" // calling [ifUnseen].\n" -" static safeToString_(object, ifUnseen) {\n" -" if (__seenByFiber == null) __seenByFiber = new Map\n" -" var seen = __seenByFiber[Fiber.current]\n" -" if (seen == null) {\n" -" __seenByFiber[Fiber.current] = seen = new List\n" -" }\n" -"\n" -" // See if we are recursing on it.\n" -" for (outer in seen) {\n" -" if (Object.same(outer, object)) return \"...\"\n" -" }\n" -"\n" -" seen.add(object)\n" -"\n" -" var result = ifUnseen.call()\n" -"\n" -" seen.removeAt(-1)\n" -" if (seen.count == 0) __seenByFiber.remove(Fiber.current)\n" -"\n" -" return result\n" -" }\n" -"\n" " bytes { new StringByteSequence(this) }\n" "}\n" "\n" @@ -190,7 +162,7 @@ static const char* coreLibSource = " return other\n" " }\n" "\n" -" toString { String.safeToString_(this) { \"[\" + join(\", \") + \"]\" } }\n" +" toString { \"[\" + join(\", \") + \"]\" }\n" "\n" " +(other) {\n" " var result = this[0..-1]\n" @@ -206,18 +178,16 @@ static const char* coreLibSource = " values { new MapValueSequence(this) }\n" "\n" " toString {\n" -" return String.safeToString_(this) {\n" -" var first = true\n" -" var result = \"{\"\n" +" var first = true\n" +" var result = \"{\"\n" "\n" -" for (key in keys) {\n" -" if (!first) result = result + \", \"\n" -" first = false\n" -" result = result + key.toString + \": \" + this[key].toString\n" -" }\n" -"\n" -" return result + \"}\"\n" +" for (key in keys) {\n" +" if (!first) result = result + \", \"\n" +" first = false\n" +" result = result + key.toString + \": \" + this[key].toString\n" " }\n" +"\n" +" return result + \"}\"\n" " }\n" "}\n" "\n" diff --git a/test/core/list/to_string.wren b/test/core/list/to_string.wren index 54b9aac1..f20d720b 100644 --- a/test/core/list/to_string.wren +++ b/test/core/list/to_string.wren @@ -14,37 +14,4 @@ class Foo { IO.print([1, new Foo, 2]) // expect: [1, Foo.toString, 2] -// Lists that directly contain themselves. -var list = [] -list.add(list) -IO.print(list) // expect: [...] - -list = [1, 2] -list[0] = list -IO.print(list) // expect: [..., 2] - -list = [1, 2] -list[1] = list -IO.print(list) // expect: [1, ...] - -// Lists that indirectly contain themselves. -list = [null, [2, [3, null, 4], null, 5], 6] -list[0] = list -list[1][1][1] = list -list[1][2] = list -IO.print(list) // expect: [..., [2, [3, ..., 4], ..., 5], 6] - -// List containing an object that calls toString on a recursive list. -class Box { - new(field) { _field = field } - toString { "box " + _field.toString } -} - -list = [1, 2] -list.add(new Box(list)) -IO.print(list) // expect: [1, 2, box ...] - -// List containing a map containing the list. -list = [1, null, 2] -list[1] = {"list": list} -IO.print(list) // expect: [1, {list: ...}, 2] +// TODO: Handle lists that contain themselves. diff --git a/test/core/map/to_string.wren b/test/core/map/to_string.wren index 0ff72822..46b6b5b9 100644 --- a/test/core/map/to_string.wren +++ b/test/core/map/to_string.wren @@ -24,28 +24,4 @@ IO.print(s == "{1: 2, 3: 4, 5: 6}" || s == "{5: 6, 1: 2, 3: 4}" || s == "{5: 6, 3: 4, 1: 2}") // expect: true -// Map that directly contains itself. -var map = {} -map["key"] = map -IO.print(map) // expect: {key: ...} - -// Map that indirectly contains itself. -map = {} -map["a"] = {"b": {"c": map}} - -IO.print(map) // expect: {a: {b: {c: ...}}} - -// Map containing an object that calls toString on a recursive map. -class Box { - new(field) { _field = field } - toString { "box " + _field.toString } -} - -map = {} -map["box"] = new Box(map) -IO.print(map) // expect: {box: box ...} - -// Map containing a list containing the map. -map = {} -map["list"] = [1, map, 2] -IO.print(map) // expect: {list: [1, ..., 2]} +// TODO: Handle maps that contain themselves. \ No newline at end of file From 3be0a396c451366b7dad2afb77864cb8629c84dc Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Tue, 19 May 2015 07:00:44 -0700 Subject: [PATCH 3/9] Remove unneeded line from Makefile. --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 383ab934..e31b44f7 100644 --- a/Makefile +++ b/Makefile @@ -57,5 +57,4 @@ gh-pages: docs amalgamation: src/include/wren.h src/vm/*.h src/vm/*.c ./script/generate_amalgamation.py > build/wren.c -.DELETE_ON_ERROR: wren.c .PHONY: all amalgamation builtin clean debug docs gh-pages release test watchdocs From a114f34a2a3532de6149291e994a2d238a58e687 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Wed, 20 May 2015 07:07:47 -0700 Subject: [PATCH 4/9] Remove outdated set example. Fix #266. --- example/set.wren | 110 ----------------------------------------------- 1 file changed, 110 deletions(-) delete mode 100644 example/set.wren diff --git a/example/set.wren b/example/set.wren deleted file mode 100644 index b13f38de..00000000 --- a/example/set.wren +++ /dev/null @@ -1,110 +0,0 @@ -class Set { - new { - _list = [] - _clean = true - } - - new (list) { - if (list is List) { - _list = list - _clean = false - cleanup - } // raise error? - } - - cleanup { - // Removes duplicates in the underlying list. - if (!_clean) { - var newList = [] - for (element in _list) { - if (!newList.contains(element)) newList.add(element) - } - _list = newList - _clean = true - } - } - - add(element) { - _clean = false - _list.add(element) - } - - remove(element) { - cleanup // Remove duplicates, so we can return early upon deletion. - for (i in 0.._list.count) { - if (_list[i] == element) { - _list.removeAt(i) - return - } - } - } - - contains(element) { - return _list.contains(element) - } - - count { - cleanup - return _list.count - } - - iterate(i) { - cleanup - if (i == null) { - if (count > 0) return 0 - return null - } - if (i < count || i >= count) return false - return i + 1 - } - - iteratorValue(i) { - cleanup - return _list[i] - } - - map(f) { - return new Set(_list.map(f)) - } - - where(f) { - return new Set(_list.where(f)) - } - - |(that) { - // Union - return new Set(_list + that) - } - - +(that) { - // A synonym for | - return this | that - } - - &(that) { - // Intersection - return new Set( - _list.where { |element| - return that.contains(element) - } + that.where { |element| - return _list.contains(element) - }) - } - - -(that) { - // Set minus - return new Set( - _list.where { |element| - return !that.contains(element) - }) - } -} - -var a = "a" -var as = new Set([a, a, a]) - -var b = "b" -var bs = new Set([b, b, b]) - -IO.write((as | bs).contains(b)) -IO.write((as & bs).contains(a)) From fc7612c84338cb00ff618b7c8727c91177b0ff1c Mon Sep 17 00:00:00 2001 From: Kyle Marek-Spartz Date: Wed, 20 May 2015 10:10:40 -0500 Subject: [PATCH 5/9] Run examples as tests. Would have prevented #266 --- example/syntax.wren | 4 ++-- script/test.py | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/example/syntax.wren b/example/syntax.wren index 423d5325..e0f17b9d 100644 --- a/example/syntax.wren +++ b/example/syntax.wren @@ -90,8 +90,8 @@ class ReservedWords is SyntaxExample { } // `foreign`, `static` - foreign static bar - foreign baz(string) + // foreign static bar + // foreign baz(string) // (Remove lines above to make this file compile) } diff --git a/script/test.py b/script/test.py index 83ed7764..302b8f80 100755 --- a/script/test.py +++ b/script/test.py @@ -65,7 +65,7 @@ def print_line(line=None): sys.stdout.flush() -def run_test(path): +def run_test(path, example=False): global passed global failed global skipped @@ -216,7 +216,9 @@ def run_test(path): for line in out_lines: if sys.version_info < (3, 0): line = line.encode('utf-8') - if expect_index >= len(expect_output): + if example: + pass + elif 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}".'. @@ -242,10 +244,15 @@ def run_test(path): print(' ' + color.PINK + fail + color.DEFAULT) print('') +def run_example(path): + return run_test(path, example=True) for dir in ['core', 'io', 'language', 'limit', 'meta']: walk(join(WREN_DIR, 'test', dir), run_test) +walk(join(WREN_DIR, 'example'), run_example) +walk(join(WREN_DIR, 'example', 'import-module'), run_example) + print_line() if failed == 0: print('All ' + color.GREEN + str(passed) + color.DEFAULT + ' tests passed.') From 5ad94fb4da472624d24be1dbbd3a25802351f3e5 Mon Sep 17 00:00:00 2001 From: Kyle Marek-Spartz Date: Thu, 21 May 2015 09:17:40 -0500 Subject: [PATCH 6/9] Add ignored to walk() in test.py to clean up list of test dirs. --- script/test.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/script/test.py b/script/test.py index 302b8f80..134a5de8 100755 --- a/script/test.py +++ b/script/test.py @@ -44,10 +44,17 @@ skipped = defaultdict(int) num_skipped = 0 -def walk(dir, callback): - """ Walks [dir], and executes [callback] on each file. """ +def walk(dir, callback, ignored=None): + """ + Walks [dir], and executes [callback] on each file unless it is [ignored]. + """ + + if not ignored: + ignored = [] + ignored += [".",".."] + dir = abspath(dir) - for file in [file for file in listdir(dir) if not file in [".",".."]]: + for file in [file for file in listdir(dir) if not file in ignored]: nfile = join(dir, file) if isdir(nfile): walk(nfile, callback) @@ -247,11 +254,8 @@ def run_test(path, example=False): def run_example(path): return run_test(path, example=True) -for dir in ['core', 'io', 'language', 'limit', 'meta']: - walk(join(WREN_DIR, 'test', dir), run_test) - +walk(join(WREN_DIR, 'test'), run_test, ignored=['benchmark']) walk(join(WREN_DIR, 'example'), run_example) -walk(join(WREN_DIR, 'example', 'import-module'), run_example) print_line() if failed == 0: From 7084d6bfd5138855be86ad19e97c34e0ce5c1914 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 24 May 2015 09:23:30 -0700 Subject: [PATCH 7/9] Add infrastructure to test embedding API. --- Makefile | 1 + script/test.py | 33 +++++++++++---- script/wren.mk | 34 +++++++++++++--- src/cli/main.c | 4 +- src/cli/vm.c | 8 ++-- src/cli/vm.h | 4 +- test/api/main.c | 58 +++++++++++++++++++++++++++ test/api/return_bool/return_bool.c | 21 ++++++++++ test/api/return_bool/return_bool.h | 3 ++ test/api/return_bool/return_bool.wren | 7 ++++ 10 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 test/api/main.c create mode 100644 test/api/return_bool/return_bool.c create mode 100644 test/api/return_bool/return_bool.h create mode 100644 test/api/return_bool/return_bool.wren diff --git a/Makefile b/Makefile index e31b44f7..0fadf053 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ clean: # Run the tests against the debug build of Wren. test: debug + @ $(MAKE) -f script/wren.mk MODE=debug test @ ./script/test.py $(suite) # Take the contents of the scripts under builtin/ and copy them into their diff --git a/script/test.py b/script/test.py index 134a5de8..31d7b71f 100755 --- a/script/test.py +++ b/script/test.py @@ -4,7 +4,7 @@ from __future__ import print_function from collections import defaultdict from os import listdir -from os.path import abspath, dirname, isdir, isfile, join, realpath, relpath, splitext +from os.path import abspath, basename, dirname, isdir, isfile, join, realpath, relpath, splitext import re from subprocess import Popen, PIPE import sys @@ -12,6 +12,7 @@ import sys # Runs the tests. WREN_DIR = dirname(dirname(realpath(__file__))) WREN_APP = join(WREN_DIR, 'bin', 'wrend') +TEST_APP = join(WREN_DIR, 'build', 'debug', 'test', 'wrend') EXPECT_PATTERN = re.compile(r'// expect: (.*)') EXPECT_ERROR_PATTERN = re.compile(r'// expect error') @@ -72,7 +73,7 @@ def print_line(line=None): sys.stdout.flush() -def run_test(path, example=False): +def run_script(app, path, type): global passed global failed global skipped @@ -155,7 +156,12 @@ def run_test(path, example=False): input_bytes = "".join(input_lines).encode("utf-8") # Invoke wren and run the test. - proc = Popen([WREN_APP, path], stdin=PIPE, stdout=PIPE, stderr=PIPE) + test_arg = path + if type == "api test": + # Just pass the suite name to API tests. + test_arg = basename(splitext(test_arg)[0]) + + proc = Popen([app, test_arg], stdin=PIPE, stdout=PIPE, stderr=PIPE) (out, err) = proc.communicate(input_bytes) fails = [] @@ -223,7 +229,9 @@ def run_test(path, example=False): for line in out_lines: if sys.version_info < (3, 0): line = line.encode('utf-8') - if example: + + if type == "example": + # Ignore output from examples. pass elif expect_index >= len(expect_output): fails.append('Got output "{0}" when none was expected.'.format(line)) @@ -251,10 +259,21 @@ def run_test(path, example=False): print(' ' + color.PINK + fail + color.DEFAULT) print('') -def run_example(path): - return run_test(path, example=True) -walk(join(WREN_DIR, 'test'), run_test, ignored=['benchmark']) +def run_test(path, example=False): + run_script(WREN_APP, path, "test") + + +def run_api_test(path): + run_script(TEST_APP, path, "api test") + + +def run_example(path): + run_script(WREN_APP, path, "example") + + +walk(join(WREN_DIR, 'test'), run_test, ignored=['api', 'benchmark']) +walk(join(WREN_DIR, 'test', 'api'), run_api_test) walk(join(WREN_DIR, 'example'), run_example) print_line() diff --git a/script/wren.mk b/script/wren.mk index a1402ea3..0b3e0fb3 100644 --- a/script/wren.mk +++ b/script/wren.mk @@ -23,6 +23,7 @@ CLI_HEADERS := $(wildcard src/cli/*.h) VM_HEADERS := $(wildcard src/vm/*.h) CLI_SOURCES := $(wildcard src/cli/*.c) VM_SOURCES := $(wildcard src/vm/*.c) +TEST_SOURCES := $(shell find test/api -name '*.c') BUILD_DIR := build C_WARNINGS := -Wall -Wextra -Werror -Wno-unused-parameter @@ -93,37 +94,58 @@ endif CFLAGS := $(C_OPTIONS) $(C_WARNINGS) -CLI_OBJECTS := $(addprefix $(BUILD_DIR)/cli/, $(notdir $(CLI_SOURCES:.c=.o))) -VM_OBJECTS := $(addprefix $(BUILD_DIR)/vm/, $(notdir $(VM_SOURCES:.c=.o))) +CLI_OBJECTS := $(addprefix $(BUILD_DIR)/cli/, $(notdir $(CLI_SOURCES:.c=.o))) +VM_OBJECTS := $(addprefix $(BUILD_DIR)/vm/, $(notdir $(VM_SOURCES:.c=.o))) +TEST_OBJECTS := $(patsubst test/api/%.c, $(BUILD_DIR)/test/%.o, $(TEST_SOURCES)) # Targets --------------------------------------------------------------------- -all: prep bin/$(WREN) lib/lib$(WREN).a lib/lib$(WREN).$(SHARED_EXT) +# Builds the VM libraries and CLI interpreter. +all: bin/$(WREN) lib/lib$(WREN).a lib/lib$(WREN).$(SHARED_EXT) -prep: - @mkdir -p bin lib $(BUILD_DIR)/cli $(BUILD_DIR)/vm +# Builds the API test executable. +test: $(BUILD_DIR)/test/$(WREN) # Command-line interpreter. bin/$(WREN): $(CLI_OBJECTS) $(VM_OBJECTS) @printf "%10s %-30s %s\n" $(CC) $@ "$(C_OPTIONS)" - @$(CC) $(CFLAGS) -Isrc/include -o $@ $^ -lm + @mkdir -p bin + @$(CC) $(CFLAGS) -o $@ $^ -lm # Static library. lib/lib$(WREN).a: $(VM_OBJECTS) @printf "%10s %-30s %s\n" $(AR) $@ "rcu" + @mkdir -p lib @$(AR) rcu $@ $^ # Shared library. lib/lib$(WREN).$(SHARED_EXT): $(VM_OBJECTS) @printf "%10s %-30s %s\n" $(CC) $@ "$(C_OPTIONS) $(SHARED_LIB_FLAGS)" + @mkdir -p lib @$(CC) $(CFLAGS) -shared $(SHARED_LIB_FLAGS) -o $@ $^ +# Test executable. +$(BUILD_DIR)/test/$(WREN): $(TEST_OBJECTS) $(VM_OBJECTS) $(BUILD_DIR)/cli/io.o $(BUILD_DIR)/cli/vm.o + @printf "%10s %-30s %s\n" $(CC) $@ "$(C_OPTIONS)" + @mkdir -p $(BUILD_DIR)/test + @$(CC) $(CFLAGS) -o $@ $^ -lm + # CLI object files. $(BUILD_DIR)/cli/%.o: src/cli/%.c $(CLI_HEADERS) $(VM_HEADERS) @printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)" + @mkdir -p $(BUILD_DIR)/cli @$(CC) -c $(CFLAGS) -Isrc/include -o $@ $(FILE_FLAG) $< # VM object files. $(BUILD_DIR)/vm/%.o: src/vm/%.c $(VM_HEADERS) @printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)" + @mkdir -p $(BUILD_DIR)/vm @$(CC) -c $(CFLAGS) -Isrc/include -o $@ $(FILE_FLAG) $< + +# Test object files. +$(BUILD_DIR)/test/%.o: test/api/%.c $(VM_HEADERS) + @printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)" + @mkdir -p $(dir $@) + @$(CC) -c $(CFLAGS) -Isrc/include -Isrc/cli -o $@ $(FILE_FLAG) $< + +.PHONY: all test diff --git a/src/cli/main.c b/src/cli/main.c index b04e9f94..2b8e5d61 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -9,7 +9,7 @@ static int runRepl() { - WrenVM* vm = createVM(); + WrenVM* vm = createVM(NULL); printf("\\\\/\"-\n"); printf(" \\_/ wren v0.0.0\n"); @@ -50,7 +50,7 @@ int main(int argc, const char* argv[]) } else if (argc == 2) { - runFile(argv[1]); + runFile(NULL, argv[1]); } return 0; diff --git a/src/cli/vm.c b/src/cli/vm.c index 4008d58d..3a62d0f7 100644 --- a/src/cli/vm.c +++ b/src/cli/vm.c @@ -4,11 +4,11 @@ #include "io.h" #include "vm.h" -WrenVM* createVM() +WrenVM* createVM(WrenBindForeignMethodFn bindForeign) { WrenConfiguration config; - config.bindForeignMethodFn = NULL; + config.bindForeignMethodFn = bindForeign; config.loadModuleFn = readModule; // Since we're running in a standalone process, be generous with memory. @@ -22,7 +22,7 @@ WrenVM* createVM() return wrenNewVM(&config); } -void runFile(const char* path) +void runFile(WrenBindForeignMethodFn bindForeign, const char* path) { // Use the directory where the file is as the root to resolve imports // relative to. @@ -42,7 +42,7 @@ void runFile(const char* path) exit(66); } - WrenVM* vm = createVM(); + WrenVM* vm = createVM(bindForeign); WrenInterpretResult result = wrenInterpret(vm, path, source); diff --git a/src/cli/vm.h b/src/cli/vm.h index 32520ded..90fa491a 100644 --- a/src/cli/vm.h +++ b/src/cli/vm.h @@ -4,11 +4,11 @@ #include "wren.h" // Creates a new Wren VM with the CLI's module loader and other configuration. -WrenVM* createVM(); +WrenVM* createVM(WrenBindForeignMethodFn bindForeign); // Executes the Wren script at [path] in a new VM. // // Exits if the script failed or could not be loaded. -void runFile(const char* path); +void runFile(WrenBindForeignMethodFn bindForeign, const char* path); #endif diff --git a/test/api/main.c b/test/api/main.c new file mode 100644 index 00000000..a7de5aa0 --- /dev/null +++ b/test/api/main.c @@ -0,0 +1,58 @@ +#include +#include + +#include "io.h" +#include "vm.h" +#include "wren.h" + +#include "return_bool/return_bool.h" + +// The name of the currently executing API test. +const char* testName; + +static WrenForeignMethodFn bindForeign( + WrenVM* vm, const char* module, const char* className, + bool isStatic, const char* signature) +{ + if (strcmp(module, "main") != 0) return NULL; + + // For convenience, concatenate all of the method qualifiers into a single + // signature string. + char fullName[256]; + fullName[0] = '\0'; + if (isStatic) strcat(fullName, "static "); + strcat(fullName, className); + strcat(fullName, "."); + strcat(fullName, signature); + + if (strcmp(testName, "return_bool") == 0) + { + return returnBoolBindForeign(fullName); + } + + fprintf(stderr, + "Unknown foreign method '%s' for test '%s'\n", fullName, testName); + return NULL; +} + +int main(int argc, const char* argv[]) +{ + if (argc != 2) + { + fprintf(stderr, "Usage: wren \n"); + return 64; // EX_USAGE. + } + + testName = argv[1]; + + // The test script is at "test/api//.wren". + char testPath[256]; + strcpy(testPath, "test/api/"); + strcat(testPath, testName); + strcat(testPath, "/"); + strcat(testPath, testName); + strcat(testPath, ".wren"); + + runFile(bindForeign, testPath); + return 0; +} diff --git a/test/api/return_bool/return_bool.c b/test/api/return_bool/return_bool.c new file mode 100644 index 00000000..e30ae061 --- /dev/null +++ b/test/api/return_bool/return_bool.c @@ -0,0 +1,21 @@ +#include + +#include "wren.h" + +static void returnTrue(WrenVM* vm) +{ + wrenReturnBool(vm, true); +} + +static void returnFalse(WrenVM* vm) +{ + wrenReturnBool(vm, false); +} + +WrenForeignMethodFn returnBoolBindForeign(const char* signature) +{ + if (strcmp(signature, "static Api.returnTrue") == 0) return returnTrue; + if (strcmp(signature, "static Api.returnFalse") == 0) return returnFalse; + + return NULL; +} diff --git a/test/api/return_bool/return_bool.h b/test/api/return_bool/return_bool.h new file mode 100644 index 00000000..f20d4986 --- /dev/null +++ b/test/api/return_bool/return_bool.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn returnBoolBindForeign(const char* signature); diff --git a/test/api/return_bool/return_bool.wren b/test/api/return_bool/return_bool.wren new file mode 100644 index 00000000..6e33ac8d --- /dev/null +++ b/test/api/return_bool/return_bool.wren @@ -0,0 +1,7 @@ +class Api { + foreign static returnTrue + foreign static returnFalse +} + +IO.print(Api.returnTrue) // expect: true +IO.print(Api.returnFalse) // expect: false From 6c135e99418f60175b00d0ddcb03cdee3369747d Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 24 May 2015 09:45:52 -0700 Subject: [PATCH 8/9] Show number of expectations in test output. --- script/test.py | 58 ++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/script/test.py b/script/test.py index 31d7b71f..c58122bd 100755 --- a/script/test.py +++ b/script/test.py @@ -24,25 +24,28 @@ STDIN_PATTERN = re.compile(r'// stdin: (.*)') 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 +expectations = 0 + + +def color_text(text, color): + """Converts text to a string and wraps it in the ANSI escape sequence for + color, if supported.""" + + # No ANSI escapes on Windows. + if sys.platform == 'win32': + return text + + return color + str(text) + '\033[0m' + + +def green(text): return color_text(text, '\033[32m') +def pink(text): return color_text(text, '\033[91m') +def red(text): return color_text(text, '\033[31m') +def yellow(text): return color_text(text, '\033[33m') def walk(dir, callback, ignored=None): @@ -78,6 +81,7 @@ def run_script(app, path, type): global failed global skipped global num_skipped + global expectations if (splitext(path)[1] != '.wren'): return @@ -103,9 +107,9 @@ def run_script(app, path, type): input_lines = [] - print_line('Passed: ' + color.GREEN + str(passed) + color.DEFAULT + - ' Failed: ' + color.RED + str(failed) + color.DEFAULT + - ' Skipped: ' + color.YELLOW + str(num_skipped) + color.DEFAULT) + print_line('Passed: ' + green(passed) + + ' Failed: ' + red(failed) + + ' Skipped: ' + yellow(num_skipped)) line_num = 1 with open(path, 'r') as file: @@ -113,18 +117,21 @@ def run_script(app, path, type): match = EXPECT_PATTERN.search(line) if match: expect_output.append((match.group(1), line_num)) + expectations += 1 match = EXPECT_ERROR_PATTERN.search(line) if match: expect_error.append(line_num) # If we expect compile errors, it should exit with EX_DATAERR. expect_return = 65 + expectations += 1 match = EXPECT_ERROR_LINE_PATTERN.search(line) if match: expect_error.append(int(match.group(1))) # If we expect compile errors, it should exit with EX_DATAERR. expect_return = 65 + expectations += 1 match = EXPECT_RUNTIME_ERROR_PATTERN.search(line) if match: @@ -132,6 +139,7 @@ def run_script(app, path, type): expect_runtime_error = match.group(1) # If we expect a runtime error, it should exit with EX_SOFTWARE. expect_return = 70 + expectations += 1 match = STDIN_PATTERN.search(line) if match: @@ -250,13 +258,12 @@ def run_script(app, path, type): # 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_line(red('FAIL') + ': ' + path) print('') for fail in fails: - print(' ' + color.PINK + fail + color.DEFAULT) + print(' ' + pink(fail)) print('') @@ -278,14 +285,13 @@ walk(join(WREN_DIR, 'example'), run_example) print_line() if failed == 0: - print('All ' + color.GREEN + str(passed) + color.DEFAULT + ' tests passed.') + print('All ' + green(passed) + ' tests passed (' + str(expectations) + + ' expectations).') else: - print(color.GREEN + str(passed) + color.DEFAULT + ' tests passed. ' + - color.RED + str(failed) + color.DEFAULT + ' tests failed.') + print(green(passed) + ' tests passed. ' + red(failed) + ' tests failed.') for key in sorted(skipped.keys()): - print('Skipped ' + color.YELLOW + str(skipped[key]) + color.DEFAULT + - ' tests: ' + key) + print('Skipped ' + yellow(skipped[key]) + ' tests: ' + key) if failed != 0: sys.exit(1) From d66556b71319ac7c927cf09ab94f295faf3e8e82 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 24 May 2015 10:04:24 -0700 Subject: [PATCH 9/9] Add API tests for returning null or numbers. --- test/api/main.c | 13 +++++++++---- test/api/return_bool/return_bool.c | 4 ++-- test/api/return_bool/return_bool.h | 2 +- test/api/return_double/return_double.c | 21 +++++++++++++++++++++ test/api/return_double/return_double.h | 3 +++ test/api/return_double/return_double.wren | 7 +++++++ test/api/return_null/return_null.c | 15 +++++++++++++++ test/api/return_null/return_null.h | 3 +++ test/api/return_null/return_null.wren | 5 +++++ 9 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 test/api/return_double/return_double.c create mode 100644 test/api/return_double/return_double.h create mode 100644 test/api/return_double/return_double.wren create mode 100644 test/api/return_null/return_null.c create mode 100644 test/api/return_null/return_null.h create mode 100644 test/api/return_null/return_null.wren diff --git a/test/api/main.c b/test/api/main.c index a7de5aa0..5724a058 100644 --- a/test/api/main.c +++ b/test/api/main.c @@ -6,6 +6,11 @@ #include "wren.h" #include "return_bool/return_bool.h" +#include "return_double/return_double.h" +#include "return_null/return_null.h" + +#define REGISTER_TEST(name) \ + if (strcmp(testName, #name) == 0) return name##BindForeign(fullName) // The name of the currently executing API test. const char* testName; @@ -25,13 +30,13 @@ static WrenForeignMethodFn bindForeign( strcat(fullName, "."); strcat(fullName, signature); - if (strcmp(testName, "return_bool") == 0) - { - return returnBoolBindForeign(fullName); - } + REGISTER_TEST(return_bool); + REGISTER_TEST(return_double); + REGISTER_TEST(return_null); fprintf(stderr, "Unknown foreign method '%s' for test '%s'\n", fullName, testName); + exit(1); return NULL; } diff --git a/test/api/return_bool/return_bool.c b/test/api/return_bool/return_bool.c index e30ae061..160db170 100644 --- a/test/api/return_bool/return_bool.c +++ b/test/api/return_bool/return_bool.c @@ -1,6 +1,6 @@ #include -#include "wren.h" +#include "return_bool.h" static void returnTrue(WrenVM* vm) { @@ -12,7 +12,7 @@ static void returnFalse(WrenVM* vm) wrenReturnBool(vm, false); } -WrenForeignMethodFn returnBoolBindForeign(const char* signature) +WrenForeignMethodFn return_boolBindForeign(const char* signature) { if (strcmp(signature, "static Api.returnTrue") == 0) return returnTrue; if (strcmp(signature, "static Api.returnFalse") == 0) return returnFalse; diff --git a/test/api/return_bool/return_bool.h b/test/api/return_bool/return_bool.h index f20d4986..21175b0c 100644 --- a/test/api/return_bool/return_bool.h +++ b/test/api/return_bool/return_bool.h @@ -1,3 +1,3 @@ #include "wren.h" -WrenForeignMethodFn returnBoolBindForeign(const char* signature); +WrenForeignMethodFn return_boolBindForeign(const char* signature); diff --git a/test/api/return_double/return_double.c b/test/api/return_double/return_double.c new file mode 100644 index 00000000..d0f317d7 --- /dev/null +++ b/test/api/return_double/return_double.c @@ -0,0 +1,21 @@ +#include + +#include "return_double.h" + +static void returnInt(WrenVM* vm) +{ + wrenReturnDouble(vm, 123456); +} + +static void returnFloat(WrenVM* vm) +{ + wrenReturnDouble(vm, 123.456); +} + +WrenForeignMethodFn return_doubleBindForeign(const char* signature) +{ + if (strcmp(signature, "static Api.returnInt") == 0) return returnInt; + if (strcmp(signature, "static Api.returnFloat") == 0) return returnFloat; + + return NULL; +} diff --git a/test/api/return_double/return_double.h b/test/api/return_double/return_double.h new file mode 100644 index 00000000..e16598df --- /dev/null +++ b/test/api/return_double/return_double.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn return_doubleBindForeign(const char* signature); diff --git a/test/api/return_double/return_double.wren b/test/api/return_double/return_double.wren new file mode 100644 index 00000000..ed59e0e9 --- /dev/null +++ b/test/api/return_double/return_double.wren @@ -0,0 +1,7 @@ +class Api { + foreign static returnInt + foreign static returnFloat +} + +IO.print(Api.returnInt) // expect: 123456 +IO.print(Api.returnFloat) // expect: 123.456 diff --git a/test/api/return_null/return_null.c b/test/api/return_null/return_null.c new file mode 100644 index 00000000..d0d47134 --- /dev/null +++ b/test/api/return_null/return_null.c @@ -0,0 +1,15 @@ +#include + +#include "return_null.h" + +static void implicitNull(WrenVM* vm) +{ + // Do nothing. +} + +WrenForeignMethodFn return_nullBindForeign(const char* signature) +{ + if (strcmp(signature, "static Api.implicitNull") == 0) return implicitNull; + + return NULL; +} diff --git a/test/api/return_null/return_null.h b/test/api/return_null/return_null.h new file mode 100644 index 00000000..8a565904 --- /dev/null +++ b/test/api/return_null/return_null.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn return_nullBindForeign(const char* signature); diff --git a/test/api/return_null/return_null.wren b/test/api/return_null/return_null.wren new file mode 100644 index 00000000..a1164ce3 --- /dev/null +++ b/test/api/return_null/return_null.wren @@ -0,0 +1,5 @@ +class Api { + foreign static implicitNull +} + +IO.print(Api.implicitNull == null) // expect: true