diff --git a/AUTHORS b/AUTHORS index 7ac75306..98c79779 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,4 +13,5 @@ Raymond Sohn Thorbjørn Lindeijer Patricio Mac Adden Evan Hahn -Starbeamrainbowlabs \ No newline at end of file +Starbeamrainbowlabs +Alexander Roper \ No newline at end of file diff --git a/README.md b/README.md index b4f724ad..a6accc01 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ var adjectives = Fiber.new { while (!adjectives.isDone) IO.print(adjectives.call()) ``` - * **Wren is small.** The codebase is about [5,000 lines][src]. You can + * **Wren is small.** The codebase is about [7,000 lines][src]. You can skim the whole thing in an afternoon. It's *small*, but not *dense*. It is readable and [lovingly-commented][nan]. @@ -53,6 +53,6 @@ involved][contribute]! [classes]: http://munificent.github.io/wren/classes.html [fibers]: http://munificent.github.io/wren/fibers.html [embedding]: http://munificent.github.io/wren/embedding-api.html -[started]: getting-started.html +[started]: http://munificent.github.io/wren/getting-started.html [browser]: http://ppvk.github.io/wren-nest/ -[contribute]: contributing.html +[contribute]: http://munificent.github.io/wren/contributing.html diff --git a/doc/site/embedding-api.markdown b/doc/site/embedding-api.markdown index ab1dc94e..2a7cb0a4 100644 --- a/doc/site/embedding-api.markdown +++ b/doc/site/embedding-api.markdown @@ -1,15 +1,115 @@ ^title Embedding API ^category reference -As an embedded scripting language, the C API your host app uses to interact -with Wren is one of the key facets of the system. It's so important that... I -haven't fleshed it out much yet. +Wren is designed to be a scripting language, so the embedding API is as +important as any of its language features. There are two (well, three) ways to get Wren into +your application: -I believe good API design can't be done in a vacuum and I haven't built many -applications that embed Wren yet, so I don't have a good testbed for the -embedding API. Now that the language itself is further along, I'm starting to -work on this, but it isn't quite there yet. Feedback and contributions are -definitely welcome! +1. **Link to static or dynamic library.** When you [build Wren][build], it + generates both shared and static libraries in `lib` that you can link to. -In the meantime, you can see the current API in -[`wren.h`](https://github.com/munificent/wren/blob/master/src/include/wren.h). +2. **Include the source directly in your application.** If you want to include + the source directly in your program, you don't need to run any build steps. + Just add the source files in `src/vm` to your project. They should compile + cleanly as C99 or C++89 or anything later. + +[build]: getting-started.html + +You also want to add `src/include` to your include path so you can get to the +[public header for Wren][wren.h]: + +[wren.h]: https://github.com/munificent/wren/blob/master/src/include/wren.h + + :::c + #include "wren.h" + +## Hosting a Wren VM + +Once you've integrated the code into your executable, you need to create a +virtual machine. To do that, you first fill in a `WrenConfiguration`: + + :::c + WrenConfiguration config; + wrenInitConfiguration(&config); + + config.reallocateFn = ...; + config.loadModuleFn = ...; + config.bindForeignMethodFn = ...; + config.bindForeignClassFn = ...; + config.initialHeapSize = ...; + config.minHeapSize = ...; + config.heapGrowthPercent = ...; + +The `reallocateFn` is a callback you can provide to control how Wren allocates +and frees memory. If you leave that to the default, it uses `malloc()` and +`free()`. + +The next three callbacks are how Wren talks back to your program. We'll cover +those in detail later. Then there a few fields to let you tune how the garbage +collector runs. You can tweak these if you want, but the defaults are usually +fine. + +Now you can create a VM: + + :::c + WrenVM* vm = wrenNewVM(&config); + +This allocates memory for a new VM using the same `reallocateFn` you provided. +The Wren C implementation has no global state, so every single bit of data Wren +uses is bundled up inside a `WrenVM`. You can have multiple Wren VMs running +independently from each other without any problems. + +`wrenNewVM()` stores its own copy of the configuration, so after calling it, you +can discard the `WrenConfiguration` struct you filled in. Now you have a live +VM, waiting to run some code! + +## Executing Wren code + +You can tell the VM to execute a string of Wren source code like so: + + :::c + WrenInterpretResult result = wrenInterpret(vm, + "", + "IO.print(\"Hi!\")"); + +The first string parameter is a "source path". It's just an arbitrary string that describes where the source code is from. It's what shows up in stack traces if a runtime error occurs in the code. It can be whatever you want as long as it's not `NULL`. + +The other string is the chunk of code to execute—a series of one or more +statements separated by newlines. Wren runs this code in a special "main" +module. Each time you call this, the code is run in the same module. This way, +top-level names defined in one call can be accessed in later ones. + +When you call `wrenInterpret()`, Wren first compiles your source to bytecode. If an error occurs here, it returns immediately with `WREN_RESULT_COMPILE_ERROR`. Otherwise, Wren spins up a new [fiber][] and executes the code in that. Your code can in turn spawn whatever other fibers it wants. It keeps running fibers until they all complete. + +[fiber]: fibers.html + +If a [runtime error][] occurs (and another fiber doesn't catch it), it will abort fibers all the way back to the main one and then return `WREN_RESULT_RUNTIME_ERROR`. Otherwise, when the last fiber successfully returns, it returns `WREN_RESULT_SUCCESS`. + +[runtime error]: error-handling.html + +## Calling a C function from Wren + +**TODO** + +## Calling a Wren method from C + +**TODO** + +## Storing a reference to a Wren object in C + +**TODO** + +## Storing C data in a Wren object + +**TODO** + +## Shutting down a VM + +Once the party is over and you're ready to end your relationship with a VM, you need to free any memory it allocated. You do that like so: + + :::c + wrenFreeVM(vm); + +After calling that, you obviously cannot use the `WrenVM*` you passed to it again. It's dead. + +Note that Wren will yell at you if you still have any live `WrenValue` or `WrenMethod` objects when you call this. This makes sure you haven't lost track of any of them (which leaks memory) and you don't try to use any of them after the VM has been freed. diff --git a/doc/site/index.markdown b/doc/site/index.markdown index 3954ad17..204b3804 100644 --- a/doc/site/index.markdown +++ b/doc/site/index.markdown @@ -20,7 +20,7 @@ a familiar, modern [syntax][]. while (!adjectives.isDone) IO.print(adjectives.call()) - * **Wren is small.** The codebase is about [5,000 lines][src]. You can + * **Wren is small.** The codebase is about [7,000 lines][src]. You can skim the whole thing in an afternoon. It's *small*, but not *dense*. It is readable and [lovingly-commented][nan]. diff --git a/doc/site/style.scss b/doc/site/style.scss index fd49f7ff..a04a4ecf 100644 --- a/doc/site/style.scss +++ b/doc/site/style.scss @@ -242,6 +242,9 @@ footer { // Strings. span.s, span.s2 { color: hsl(40, 80%, 50%); } span.se { color: hsl(30, 80%, 50%); } + + // Preprocessor directives. + span.cp { color: hsl(270, 40%, 60%); } } // Have a different primary color for the core library docs. diff --git a/project/xcode/wren.xcodeproj/project.pbxproj b/project/xcode/wren.xcodeproj/project.pbxproj index 09515484..e075920c 100644 --- a/project/xcode/wren.xcodeproj/project.pbxproj +++ b/project/xcode/wren.xcodeproj/project.pbxproj @@ -50,7 +50,7 @@ ); runOnlyForDeploymentPostprocessing = 1; }; - 29CE20081B7256660057FBA3 /* CopyFiles */ = { + 29D0099D1B7E397D000CE58C /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; @@ -90,14 +90,14 @@ 29C8A9301AB71C3300DEC81D /* io.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = io.h; path = ../../src/cli/io.h; sourceTree = ""; }; 29C8A9311AB71FFF00DEC81D /* vm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vm.c; path = ../../src/cli/vm.c; sourceTree = ""; }; 29C8A9321AB71FFF00DEC81D /* vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vm.h; path = ../../src/cli/vm.h; sourceTree = ""; }; - 29CE200A1B7256660057FBA3 /* api_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = api_test; sourceTree = BUILT_PRODUCTS_DIR; }; - 29CE20171B7257720057FBA3 /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../test/api/main.c; sourceTree = ""; }; - 29CE20191B72577C0057FBA3 /* return_bool.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = return_bool.c; path = ../../test/api/return_bool/return_bool.c; sourceTree = ""; }; - 29CE201A1B72577C0057FBA3 /* return_bool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = return_bool.h; path = ../../test/api/return_bool/return_bool.h; sourceTree = ""; }; - 29CE201C1B7257840057FBA3 /* return_double.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = return_double.c; path = ../../test/api/return_double/return_double.c; sourceTree = ""; }; - 29CE201D1B7257840057FBA3 /* return_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = return_double.h; path = ../../test/api/return_double/return_double.h; sourceTree = ""; }; - 29CE201F1B72578C0057FBA3 /* return_null.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = return_null.c; path = ../../test/api/return_null/return_null.c; sourceTree = ""; }; - 29CE20201B72578C0057FBA3 /* return_null.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = return_null.h; path = ../../test/api/return_null/return_null.h; sourceTree = ""; }; + 29D0099F1B7E397D000CE58C /* api_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = api_test; sourceTree = BUILT_PRODUCTS_DIR; }; + 29D009A61B7E3993000CE58C /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../test/api/main.c; sourceTree = ""; }; + 29D009A81B7E39A8000CE58C /* foreign_class.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = foreign_class.c; path = ../../test/api/foreign_class.c; sourceTree = ""; }; + 29D009A91B7E39A8000CE58C /* foreign_class.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = foreign_class.h; path = ../../test/api/foreign_class.h; sourceTree = ""; }; + 29D009AA1B7E39A8000CE58C /* returns.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = returns.c; path = ../../test/api/returns.c; sourceTree = ""; }; + 29D009AB1B7E39A8000CE58C /* returns.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = returns.h; path = ../../test/api/returns.h; sourceTree = ""; }; + 29D009AC1B7E39A8000CE58C /* value.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = value.c; path = ../../test/api/value.c; sourceTree = ""; }; + 29D009AD1B7E39A8000CE58C /* value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = value.h; path = ../../test/api/value.h; sourceTree = ""; }; 29DE39511AC3A50A00987D41 /* wren_meta.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_meta.c; path = ../../src/vm/wren_meta.c; sourceTree = ""; }; 29DE39521AC3A50A00987D41 /* wren_meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_meta.h; path = ../../src/vm/wren_meta.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -119,6 +119,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 29D0099C1B7E397D000CE58C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -186,7 +193,7 @@ 29205CAA1AB4E6840073018D /* include */, 2901D7611B74F3E20083A2C8 /* module */, 29205CA01AB4E6470073018D /* vm */, - 29CE20161B7257680057FBA3 /* api */, + 29D0099A1B7E394F000CE58C /* api_test */, 29AB1F071816E3AD004B501E /* Products */, ); sourceTree = ""; @@ -200,18 +207,18 @@ name = Products; sourceTree = ""; }; - 29CE20161B7257680057FBA3 /* api */ = { + 29D0099A1B7E394F000CE58C /* api_test */ = { isa = PBXGroup; children = ( - 29CE20171B7257720057FBA3 /* main.c */, - 29CE20191B72577C0057FBA3 /* return_bool.c */, - 29CE201A1B72577C0057FBA3 /* return_bool.h */, - 29CE201C1B7257840057FBA3 /* return_double.c */, - 29CE201D1B7257840057FBA3 /* return_double.h */, - 29CE201F1B72578C0057FBA3 /* return_null.c */, - 29CE20201B72578C0057FBA3 /* return_null.h */, + 29D009A61B7E3993000CE58C /* main.c */, + 29D009A81B7E39A8000CE58C /* foreign_class.c */, + 29D009A91B7E39A8000CE58C /* foreign_class.h */, + 29D009AA1B7E39A8000CE58C /* returns.c */, + 29D009AB1B7E39A8000CE58C /* returns.h */, + 29D009AC1B7E39A8000CE58C /* value.c */, + 29D009AD1B7E39A8000CE58C /* value.h */, ); - name = api; + name = api_test; sourceTree = ""; }; /* End PBXGroup section */ @@ -234,13 +241,13 @@ productReference = 29AB1F061816E3AD004B501E /* wren */; productType = "com.apple.product-type.tool"; }; - 29CE20091B7256660057FBA3 /* api_test */ = { + 29D0099E1B7E397D000CE58C /* api_test */ = { isa = PBXNativeTarget; - buildConfigurationList = 29CE20101B7256660057FBA3 /* Build configuration list for PBXNativeTarget "api_test" */; + buildConfigurationList = 29D009A31B7E397D000CE58C /* Build configuration list for PBXNativeTarget "api_test" */; buildPhases = ( - 29CE20061B7256660057FBA3 /* Sources */, - 29CE20071B7256660057FBA3 /* Frameworks */, - 29CE20081B7256660057FBA3 /* CopyFiles */, + 29D0099B1B7E397D000CE58C /* Sources */, + 29D0099C1B7E397D000CE58C /* Frameworks */, + 29D0099D1B7E397D000CE58C /* CopyFiles */, ); buildRules = ( ); @@ -248,7 +255,7 @@ ); name = api_test; productName = api_test; - productReference = 29CE200A1B7256660057FBA3 /* api_test */; + productReference = 29D0099F1B7E397D000CE58C /* api_test */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ @@ -260,7 +267,7 @@ LastUpgradeCheck = 0610; ORGANIZATIONNAME = "Bob Nystrom"; TargetAttributes = { - 29CE20091B7256660057FBA3 = { + 29D0099E1B7E397D000CE58C = { CreatedOnToolsVersion = 6.3.2; }; }; @@ -278,7 +285,7 @@ projectRoot = ""; targets = ( 29AB1F051816E3AD004B501E /* wren */, - 29CE20091B7256660057FBA3 /* api_test */, + 29D0099E1B7E397D000CE58C /* api_test */, ); }; /* End PBXProject section */ @@ -304,26 +311,25 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 29CE20061B7256660057FBA3 /* Sources */ = { + 29D0099B1B7E397D000CE58C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 29CE20221B72586B0057FBA3 /* io.c in Sources */, - 29CE20231B72586B0057FBA3 /* vm.c in Sources */, - 29CE20241B72586B0057FBA3 /* wren_compiler.c in Sources */, - 29CE20251B72586B0057FBA3 /* wren_core.c in Sources */, - 29CE20261B72586B0057FBA3 /* wren_debug.c in Sources */, - 29CE20271B72586B0057FBA3 /* wren_io.c in Sources */, - 29CE20281B72586B0057FBA3 /* wren_meta.c in Sources */, - 29CE20291B72586B0057FBA3 /* wren_primitive.c in Sources */, - 29CE202A1B72586B0057FBA3 /* wren_utils.c in Sources */, - 2901D7651B74F4050083A2C8 /* timer.c in Sources */, - 29CE202B1B72586B0057FBA3 /* wren_value.c in Sources */, - 29CE202C1B72586B0057FBA3 /* wren_vm.c in Sources */, - 29CE20181B7257720057FBA3 /* main.c in Sources */, - 29CE201B1B72577C0057FBA3 /* return_bool.c in Sources */, - 29CE201E1B7257840057FBA3 /* return_double.c in Sources */, - 29CE20211B72578C0057FBA3 /* return_null.c in Sources */, + 29D009B11B7E39FE000CE58C /* io.c in Sources */, + 29D009B21B7E39FE000CE58C /* vm.c in Sources */, + 29D009B31B7E39FE000CE58C /* wren_compiler.c in Sources */, + 29D009B41B7E39FE000CE58C /* wren_core.c in Sources */, + 29D009B51B7E39FE000CE58C /* wren_debug.c in Sources */, + 29D009B61B7E39FE000CE58C /* wren_io.c in Sources */, + 29D009B71B7E39FE000CE58C /* wren_meta.c in Sources */, + 29D009B81B7E39FE000CE58C /* wren_primitive.c in Sources */, + 29D009B91B7E39FE000CE58C /* wren_utils.c in Sources */, + 29D009BA1B7E39FE000CE58C /* wren_value.c in Sources */, + 29D009BB1B7E39FE000CE58C /* wren_vm.c in Sources */, + 29D009A71B7E3993000CE58C /* main.c in Sources */, + 29D009B01B7E39A8000CE58C /* value.c in Sources */, + 29D009AF1B7E39A8000CE58C /* returns.c in Sources */, + 29D009AE1B7E39A8000CE58C /* foreign_class.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -470,6 +476,46 @@ }; name = Release; }; + 29D009A41B7E397D000CE58C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_PEDANTIC = NO; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 29D009A51B7E397D000CE58C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_PEDANTIC = NO; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -491,14 +537,13 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 29CE20101B7256660057FBA3 /* Build configuration list for PBXNativeTarget "api_test" */ = { + 29D009A31B7E397D000CE58C /* Build configuration list for PBXNativeTarget "api_test" */ = { isa = XCConfigurationList; buildConfigurations = ( - 29CE200E1B7256660057FBA3 /* Debug */, - 29CE200F1B7256660057FBA3 /* Release */, + 29D009A41B7E397D000CE58C /* Debug */, + 29D009A51B7E397D000CE58C /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/script/benchmark.py b/script/benchmark.py index 30f704c5..f346c720 100755 --- a/script/benchmark.py +++ b/script/benchmark.py @@ -59,7 +59,7 @@ BENCHMARK("binary_trees", """stretch tree of depth 13 check: -1 32 trees of depth 12 check: -32 long lived tree of depth 12 check: -1""") -BENCHMARK("delta_blue", "7032700") +BENCHMARK("delta_blue", "14065400") BENCHMARK("fib", r"""317811 317811 diff --git a/script/generate_docs.py b/script/generate_docs.py index ac56c311..67b91f77 100755 --- a/script/generate_docs.py +++ b/script/generate_docs.py @@ -14,6 +14,13 @@ from datetime import datetime import markdown +# Match a "## " style header. We require a space after "#" to avoid +# accidentally matching "#include" in code samples. +MARKDOWN_HEADER = re.compile(r'#+ ') + +# Clean up a header to be a valid URL. +FORMAT_ANCHOR = re.compile(r'\.|\?|!|:|/|\*') + with codecs.open("doc/site/template.html", encoding="utf-8") as f: template = f.read() @@ -70,13 +77,13 @@ def format_file(path, skip_up_to_date): else: print(' '.join(["UNKNOWN COMMAND:", command, args])) - elif stripped.startswith('#'): + elif MARKDOWN_HEADER.match(stripped): # Add anchors to the headers. index = stripped.find(" ") headertype = stripped[:index] header = stripped[index:].strip() anchor = header.lower().replace(' ', '-') - anchor = re.compile(r'\.|\?|!|:|/|\*').sub('', anchor) + anchor = FORMAT_ANCHOR.sub('', anchor) contents += indentation + headertype contents += '{1} #\n'.format(anchor, header) diff --git a/script/metrics.py b/script/metrics.py index 8f897f73..a8a1d85e 100755 --- a/script/metrics.py +++ b/script/metrics.py @@ -43,7 +43,11 @@ for source_path in files: num_docs += 1 continue - if (line.strip() == ""): + stripped = line.strip() + # Don't count { or } lines since Wren's coding style puts them on their + # own lines but they don't add anything meaningful to the length of the + # program. + if (stripped == "" or stripped == "{" or stripped == "}"): num_empty += 1 continue diff --git a/script/wren.mk b/script/wren.mk index 2feb61cd..2dc5bb54 100644 --- a/script/wren.mk +++ b/script/wren.mk @@ -28,7 +28,9 @@ MODULE_SOURCES := $(wildcard src/module/*.c) VM_HEADERS := $(wildcard src/vm/*.h) VM_SOURCES := $(wildcard src/vm/*.c) -TEST_SOURCES := $(shell find test/api -name '*.c') +TEST_HEADERS := $(wildcard test/api/*.h) +TEST_SOURCES := $(wildcard test/api/*.c) + BUILD_DIR := build C_WARNINGS := -Wall -Wextra -Werror -Wno-unused-parameter @@ -177,7 +179,7 @@ $(BUILD_DIR)/vm/%.o: src/vm/%.c $(VM_HEADERS) @ $(CC) -c $(CFLAGS) -Isrc/include -o $@ $(FILE_FLAG) $< # Test object files. -$(BUILD_DIR)/test/%.o: test/api/%.c $(MODULE_HEADERS) $(VM_HEADERS) $(LIBUV) +$(BUILD_DIR)/test/%.o: test/api/%.c $(MODULE_HEADERS) $(VM_HEADERS) $(TEST_HEADERS) $(LIBUV) @ printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)" @ mkdir -p $(dir $@) @ $(CC) -c $(CFLAGS) $(CLI_FLAGS) -o $@ $(FILE_FLAG) $< diff --git a/src/cli/main.c b/src/cli/main.c index e5fb9160..cb2a7b99 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -12,14 +12,14 @@ int main(int argc, const char* argv[]) fprintf(stderr, "Usage: wren [file]\n"); return 64; // EX_USAGE. } - + if (argc == 1) { runRepl(); } else if (argc == 2) { - runFile(argv[1], NULL); + runFile(argv[1]); } return 0; diff --git a/src/cli/vm.c b/src/cli/vm.c index 400336bc..14c6de8b 100644 --- a/src/cli/vm.c +++ b/src/cli/vm.c @@ -10,46 +10,59 @@ // The single VM instance that the CLI uses. WrenVM* vm; -WrenBindForeignMethodFn externalBindForeign; +static WrenBindForeignMethodFn bindMethodFn = NULL; +static WrenBindForeignClassFn bindClassFn = NULL; uv_loop_t* loop; -static WrenForeignMethodFn bindForeignMethod(WrenVM* vm, - const char* module, - const char* className, - bool isStatic, - const char* signature) +/// Binds foreign methods declared in either built in modules, or the injected +/// API test modules. +static WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module, + const char* className, bool isStatic, const char* signature) { if (strcmp(module, "timer") == 0) { return timerBindForeign(vm, className, isStatic, signature); } - - if (externalBindForeign != NULL) + + if (bindMethodFn != NULL) { - return externalBindForeign(vm, module, className, isStatic, signature); + return bindMethodFn(vm, module, className, isStatic, signature); } - + return NULL; } +/// Binds foreign classes declared in either built in modules, or the injected +/// API test modules. +static WrenForeignClassMethods bindForeignClass( + WrenVM* vm, const char* module, const char* className) +{ + WrenForeignClassMethods methods = { NULL, NULL }; + + // TODO: Bind classes for built-in modules here. + + if (bindClassFn != NULL) + { + return bindClassFn(vm, module, className); + } + + return methods; +} + static void initVM() { WrenConfiguration config; + wrenInitConfiguration(&config); config.bindForeignMethodFn = bindForeignMethod; + config.bindForeignClassFn = bindForeignClass; config.loadModuleFn = readModule; // Since we're running in a standalone process, be generous with memory. config.initialHeapSize = 1024 * 1024 * 100; - - // Use defaults for these. - config.reallocateFn = NULL; - config.minHeapSize = 0; - config.heapGrowthPercent = 0; - vm = wrenNewVM(&config); - + // Initialize the event loop. loop = (uv_loop_t*)malloc(sizeof(uv_loop_t)); uv_loop_init(loop); @@ -58,17 +71,15 @@ static void initVM() static void freeVM() { timerReleaseMethods(); - + uv_loop_close(loop); free(loop); wrenFreeVM(vm); } -void runFile(const char* path, WrenBindForeignMethodFn bindForeign) +void runFile(const char* path) { - externalBindForeign = bindForeign; - // Use the directory where the file is as the root to resolve imports // relative to. char* root = NULL; @@ -87,18 +98,18 @@ void runFile(const char* path, WrenBindForeignMethodFn bindForeign) fprintf(stderr, "Could not find file \"%s\".\n", path); exit(66); } - + initVM(); - + WrenInterpretResult result = wrenInterpret(vm, path, source); - + if (result == WREN_RESULT_SUCCESS) { uv_run(loop, UV_RUN_DEFAULT); } freeVM(); - + free(source); free(root); @@ -110,30 +121,30 @@ void runFile(const char* path, WrenBindForeignMethodFn bindForeign) int runRepl() { initVM(); - + printf("\\\\/\"-\n"); printf(" \\_/ wren v0.0.0\n"); - + char line[MAX_LINE_LENGTH]; - + for (;;) { printf("> "); - + if (!fgets(line, MAX_LINE_LENGTH, stdin)) { printf("\n"); break; } - + // TODO: Handle failure. wrenInterpret(vm, "Prompt", line); - + // TODO: Automatically print the result of expressions. } - + freeVM(); - + return 0; } @@ -146,3 +157,10 @@ uv_loop_t* getLoop() { return loop; } + +void setForeignCallbacks( + WrenBindForeignMethodFn bindMethod, WrenBindForeignClassFn bindClass) +{ + bindMethodFn = bindMethod; + bindClassFn = bindClass; +} diff --git a/src/cli/vm.h b/src/cli/vm.h index 064d07d0..1ccf86b9 100644 --- a/src/cli/vm.h +++ b/src/cli/vm.h @@ -6,9 +6,8 @@ // Executes the Wren script at [path] in a new VM. // -// If [bindForeign] is not `NULL`, it will be called to register any foreign -// methods that the CLI itself doesn't handle. -void runFile(const char* path, WrenBindForeignMethodFn bindForeign); +// Exits if the script failed or could not be loaded. +void runFile(const char* path); // Runs the Wren interactive REPL. int runRepl(); @@ -19,4 +18,11 @@ WrenVM* getVM(); // Gets the event loop the VM is using. uv_loop_t* getLoop(); +// Adds additional callbacks to use when binding foreign members from Wren. +// +// Used by the API test executable to let it wire up its own foreign functions. +// This must be called before calling [createVM()]. +void setForeignCallbacks(WrenBindForeignMethodFn bindMethod, + WrenBindForeignClassFn bindClass); + #endif diff --git a/src/include/wren.h b/src/include/wren.h index d08d9d0a..e13291fb 100644 --- a/src/include/wren.h +++ b/src/include/wren.h @@ -53,6 +53,26 @@ typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, bool isStatic, const char* signature); +typedef struct +{ + // The callback invoked when the foreign object is created. + // + // This must be provided. Inside the body of this, it must call + // [wrenAllocateForeign] exactly once. + WrenForeignMethodFn allocate; + + // The callback invoked when the garbage collector is about to collecto a + // foreign object's memory. + // + // This may be `NULL` if the foreign class does not need to finalize. + WrenForeignMethodFn finalize; +} WrenForeignClassMethods; + +// Returns a pair of pointers to the foreign methods used to allocate and +// finalize the data for instances of [className] in [module]. +typedef WrenForeignClassMethods (*WrenBindForeignClassFn)( + WrenVM* vm, const char* module, const char* className); + typedef struct { // The callback Wren will use to allocate, reallocate, and deallocate memory. @@ -88,6 +108,14 @@ typedef struct // Wren will report it as runtime error. WrenBindForeignMethodFn bindForeignMethodFn; + // The callback Wren uses to find a foreign class and get its foreign methods. + // + // When a foreign class is declared, this will be called with the class's + // module and name when the class body is executed. It should return the + // foreign functions uses to allocate and (optionally) finalize the bytes + // stored in the foreign object when an instance is created. + WrenBindForeignClassFn bindForeignClassFn; + // The number of bytes Wren will allocate before triggering the first garbage // collection. // @@ -129,6 +157,11 @@ typedef enum { WREN_RESULT_RUNTIME_ERROR } WrenInterpretResult; +// Initializes [configuration] with all of its default values. +// +// Call this before setting the particular fields you care about. +void wrenInitConfiguration(WrenConfiguration* configuration); + // Creates a new Wren virtual machine using the given [configuration]. Wren // will copy the configuration data, so the argument passed to this can be // freed after calling this. If [configuration] is `NULL`, uses a default @@ -186,6 +219,16 @@ void wrenReleaseMethod(WrenVM* vm, WrenMethod* method); // longer be used. void wrenReleaseValue(WrenVM* vm, WrenValue* value); +// This must be called once inside a foreign class's allocator function. +// +// It tells Wren how many bytes of raw data need to be stored in the foreign +// object and creates the new object with that size. It returns a pointer to +// the foreign object's data. +void* wrenAllocateForeign(WrenVM* vm, size_t size); + +// Returns the number of arguments available to the current foreign method. +int wrenGetArgumentCount(WrenVM* vm); + // The following functions read one of the arguments passed to a foreign call. // They may only be called while within a function provided to // [wrenDefineMethod] or [wrenDefineStaticMethod] that Wren has invoked. @@ -214,6 +257,11 @@ bool wrenGetArgumentBool(WrenVM* vm, int index); // a number. double wrenGetArgumentDouble(WrenVM* vm, int index); +// Reads a foreign object argument for a foreign call and returns a pointer to +// the foreign data stored with it. Returns NULL if the argument is not a +// foreign object. +void* wrenGetArgumentForeign(WrenVM* vm, int index); + // Reads an string argument for a foreign call. Returns NULL if the argument is // not a string. // diff --git a/src/vm/wren_common.h b/src/vm/wren_common.h index a8a82108..834f7eb8 100644 --- a/src/vm/wren_common.h +++ b/src/vm/wren_common.h @@ -99,10 +99,10 @@ // foo() // No-argument method. // foo(_) // One-argument method. // foo(_,_) // Two-argument method. -// this foo() // Constructor initializer. +// init foo() // Constructor initializer. // // The maximum signature length takes into account the longest method name, the -// maximum number of parameters with separators between them, "this ", and "()". +// maximum number of parameters with separators between them, "init ", and "()". #define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 6) // The maximum length of an identifier. The only real reason for this limitation diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index ba4e8e59..48cc5659 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -154,14 +154,9 @@ typedef struct // If a syntax or compile error has occurred. bool hasError; - - // A buffer for the unescaped text of the current token if it's a string - // literal. Unlike the raw token, this will have escape sequences translated - // to their literal equivalent. - ByteBuffer string; - - // If a number literal is currently being parsed this will hold its value. - double number; + + // The parsed value if the current token is a literal. + Value value; } Parser; typedef struct @@ -252,6 +247,9 @@ typedef struct // Symbol table for the fields of the class. SymbolTable* fields; + // True if the class being compiled is a foreign class. + bool isForeign; + // True if the current method being compiled is static. bool inStatic; @@ -369,6 +367,8 @@ static void error(Compiler* compiler, const char* format, ...) // Adds [constant] to the constant pool and returns its index. static int addConstant(Compiler* compiler, Value constant) { + if (compiler->parser->hasError) return -1; + if (compiler->constants.count < MAX_CONSTANTS) { if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant)); @@ -476,6 +476,14 @@ static char nextChar(Parser* parser) return c; } +// If the current character is [c], consumes it and returns `true`. +static bool matchChar(Parser* parser, char c) +{ + if (peekChar(parser) != c) return false; + nextChar(parser); + return true; +} + // Sets the parser's current token to the given [type] and current character // range. static void makeToken(Parser* parser, TokenType type) @@ -493,14 +501,7 @@ static void makeToken(Parser* parser, TokenType type) // [two]. Otherwise makes a token of type [one]. static void twoCharToken(Parser* parser, char c, TokenType two, TokenType one) { - if (peekChar(parser) == c) - { - nextChar(parser); - makeToken(parser, two); - return; - } - - makeToken(parser, one); + makeToken(parser, matchChar(parser, c) ? two : one); } // Skips the rest of the current line. @@ -515,8 +516,6 @@ static void skipLineComment(Parser* parser) // Skips the rest of a block comment. static void skipBlockComment(Parser* parser) { - nextChar(parser); // The opening "*". - int nesting = 1; while (nesting > 0) { @@ -578,13 +577,13 @@ static void makeNumber(Parser* parser, bool isHex) // We don't check that the entire token is consumed because we've already // scanned it ourselves and know it's valid. - parser->number = isHex ? strtol(parser->tokenStart, NULL, 16) - : strtod(parser->tokenStart, NULL); + parser->value = NUM_VAL(isHex ? strtol(parser->tokenStart, NULL, 16) + : strtod(parser->tokenStart, NULL)); if (errno == ERANGE) { lexError(parser, "Number literal was too large."); - parser->number = 0; + parser->value = NUM_VAL(0); } makeToken(parser, TOKEN_NUMBER); @@ -605,7 +604,6 @@ static void readHexNumber(Parser* parser) // Finishes lexing a number literal. static void readNumber(Parser* parser) { - // TODO: scientific, etc. while (isDigit(peekChar(parser))) nextChar(parser); // See if it has a floating point. Make sure there is a digit after the "." @@ -615,6 +613,20 @@ static void readNumber(Parser* parser) nextChar(parser); while (isDigit(peekChar(parser))) nextChar(parser); } + + // See if the number is in scientific notation. + if (matchChar(parser, 'e') || matchChar(parser, 'E')) + { + // Allow a negative exponent. + matchChar(parser, '-'); + + if (!isDigit(peekChar(parser))) + { + lexError(parser, "Unterminated scientific notation."); + } + + while (isDigit(peekChar(parser))) nextChar(parser); + } makeNumber(parser, false); } @@ -650,12 +662,6 @@ static void readName(Parser* parser, TokenType type) makeToken(parser, type); } -// Adds [c] to the current string literal being tokenized. -static void addStringChar(Parser* parser, char c) -{ - wrenByteBufferWrite(parser->vm, &parser->string, c); -} - // Reads [digits] hex digits in a string literal and returns their number value. static int readHexEscape(Parser* parser, int digits, const char* description) { @@ -686,7 +692,7 @@ static int readHexEscape(Parser* parser, int digits, const char* description) } // Reads a four hex digit Unicode escape sequence in a string literal. -static void readUnicodeEscape(Parser* parser) +static void readUnicodeEscape(Parser* parser, ByteBuffer* string) { int value = readHexEscape(parser, 4, "Unicode"); @@ -694,16 +700,16 @@ static void readUnicodeEscape(Parser* parser) int numBytes = wrenUtf8NumBytes(value); if (numBytes != 0) { - wrenByteBufferFill(parser->vm, &parser->string, 0, numBytes); - wrenUtf8Encode(value, - parser->string.data + parser->string.count - numBytes); + wrenByteBufferFill(parser->vm, string, 0, numBytes); + wrenUtf8Encode(value, string->data + string->count - numBytes); } } // Finishes lexing a string literal. static void readString(Parser* parser) { - wrenByteBufferClear(parser->vm, &parser->string); + ByteBuffer string; + wrenByteBufferInit(&string); for (;;) { @@ -724,20 +730,21 @@ static void readString(Parser* parser) { switch (nextChar(parser)) { - case '"': addStringChar(parser, '"'); break; - case '\\': addStringChar(parser, '\\'); break; - case '0': addStringChar(parser, '\0'); break; - case 'a': addStringChar(parser, '\a'); break; - case 'b': addStringChar(parser, '\b'); break; - case 'f': addStringChar(parser, '\f'); break; - case 'n': addStringChar(parser, '\n'); break; - case 'r': addStringChar(parser, '\r'); break; - case 't': addStringChar(parser, '\t'); break; - case 'u': readUnicodeEscape(parser); break; + case '"': wrenByteBufferWrite(parser->vm, &string, '"'); break; + case '\\': wrenByteBufferWrite(parser->vm, &string, '\\'); break; + case '0': wrenByteBufferWrite(parser->vm, &string, '\0'); break; + case 'a': wrenByteBufferWrite(parser->vm, &string, '\a'); break; + case 'b': wrenByteBufferWrite(parser->vm, &string, '\b'); break; + case 'f': wrenByteBufferWrite(parser->vm, &string, '\f'); break; + case 'n': wrenByteBufferWrite(parser->vm, &string, '\n'); break; + case 'r': wrenByteBufferWrite(parser->vm, &string, '\r'); break; + case 't': wrenByteBufferWrite(parser->vm, &string, '\t'); break; + case 'u': readUnicodeEscape(parser, &string); break; // TODO: 'U' for 8 octet Unicode escapes. - case 'v': addStringChar(parser, '\v'); break; + case 'v': wrenByteBufferWrite(parser->vm, &string, '\v'); break; case 'x': - addStringChar(parser, (uint8_t)readHexEscape(parser, 2, "byte")); + wrenByteBufferWrite(parser->vm, &string, + (uint8_t)readHexEscape(parser, 2, "byte")); break; default: @@ -748,10 +755,12 @@ static void readString(Parser* parser) } else { - addStringChar(parser, c); + wrenByteBufferWrite(parser->vm, &string, c); } } + parser->value = wrenNewString(parser->vm, (char*)string.data, string.count); + wrenByteBufferClear(parser->vm, &string); makeToken(parser, TOKEN_STRING); } @@ -779,38 +788,38 @@ static void nextToken(Parser* parser) case '{': makeToken(parser, TOKEN_LEFT_BRACE); return; case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return; case ':': makeToken(parser, TOKEN_COLON); return; - case '.': - if (peekChar(parser) == '.') - { - nextChar(parser); - if (peekChar(parser) == '.') - { - nextChar(parser); - makeToken(parser, TOKEN_DOTDOTDOT); - return; - } - - makeToken(parser, TOKEN_DOTDOT); - return; - } - - makeToken(parser, TOKEN_DOT); - return; - case ',': makeToken(parser, TOKEN_COMMA); return; case '*': makeToken(parser, TOKEN_STAR); return; case '%': makeToken(parser, TOKEN_PERCENT); return; + case '^': makeToken(parser, TOKEN_CARET); return; case '+': makeToken(parser, TOKEN_PLUS); return; + case '-': makeToken(parser, TOKEN_MINUS); return; case '~': makeToken(parser, TOKEN_TILDE); return; case '?': makeToken(parser, TOKEN_QUESTION); return; + + case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return; + case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return; + case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return; + case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return; + + case '.': + if (matchChar(parser, '.')) + { + twoCharToken(parser, '.', TOKEN_DOTDOTDOT, TOKEN_DOTDOT); + return; + } + + makeToken(parser, TOKEN_DOT); + return; + case '/': - if (peekChar(parser) == '/') + if (matchChar(parser, '/')) { skipLineComment(parser); break; } - if (peekChar(parser) == '*') + if (matchChar(parser, '*')) { skipBlockComment(parser); break; @@ -819,30 +828,9 @@ static void nextToken(Parser* parser) makeToken(parser, TOKEN_SLASH); return; - case '-': - makeToken(parser, TOKEN_MINUS); - return; - - case '|': - twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); - return; - - case '&': - twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); - return; - - case '^': - makeToken(parser, TOKEN_CARET); - return; - - case '=': - twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); - return; - case '<': - if (peekChar(parser) == '<') + if (matchChar(parser, '<')) { - nextChar(parser); makeToken(parser, TOKEN_LTLT); } else @@ -852,9 +840,8 @@ static void nextToken(Parser* parser) return; case '>': - if (peekChar(parser) == '>') + if (matchChar(parser, '>')) { - nextChar(parser); makeToken(parser, TOKEN_GTGT); } else @@ -863,10 +850,6 @@ static void nextToken(Parser* parser) } return; - case '!': - twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); - return; - case '\n': makeToken(parser, TOKEN_LINE); return; @@ -974,9 +957,7 @@ static bool matchLine(Compiler* compiler) return true; } -// Consumes the current token if its type is [expected]. Returns true if a -// token was consumed. Since [expected] is known to be in the middle of an -// expression, any newlines following it are consumed and discarded. +// Discards any newlines starting at the current token. static void ignoreNewlines(Compiler* compiler) { matchLine(compiler); @@ -1037,9 +1018,19 @@ static int emitJump(Compiler* compiler, Code instruction) return emit(compiler, 0xff) - 1; } +// Creates a new constant for the current value and emits the bytecode to load +// it from the constant table. +static void emitConstant(Compiler* compiler) +{ + int constant = addConstant(compiler, compiler->parser->value); + + // Compile the code to load the constant. + emitShortArg(compiler, CODE_CONSTANT, constant); +} + // Create a new local variable with [name]. Assumes the current scope is local // and the name is unique. -static int defineLocal(Compiler* compiler, const char* name, int length) +static int addLocal(Compiler* compiler, const char* name, int length) { Local* local = &compiler->locals[compiler->numLocals]; local->name = name; @@ -1105,7 +1096,7 @@ static int declareVariable(Compiler* compiler, Token* token) return -1; } - return defineLocal(compiler, token->start, token->length); + return addLocal(compiler, token->start, token->length); } // Parses a name token and declares a variable in the current scope with that @@ -1595,7 +1586,7 @@ static void signatureToString(Signature* signature, break; case SIG_INITIALIZER: - memcpy(name, "this ", 5); + memcpy(name, "init ", 5); memcpy(name + 5, signature->name, signature->length); *length = 5 + signature->length; signatureParameterList(name, length, signature->arity, '(', ')'); @@ -1671,8 +1662,7 @@ static void callSignature(Compiler* compiler, Code instruction, // superclass in a constant. So, here, we create a slot in the constant // table and store NULL in it. When the method is bound, we'll look up the // superclass then and store it in the constant slot. - int constant = addConstant(compiler, NULL_VAL); - emitShort(compiler, constant); + emitShort(compiler, addConstant(compiler, NULL_VAL)); } } @@ -1938,6 +1928,10 @@ static void field(Compiler* compiler, bool allowAssignment) { error(compiler, "Cannot reference a field outside of a class definition."); } + else if (enclosingClass->isForeign) + { + error(compiler, "Cannot define fields in a foreign class."); + } else if (enclosingClass->inStatic) { error(compiler, "Cannot use an instance field in a static method."); @@ -2128,32 +2122,10 @@ static void null(Compiler* compiler, bool allowAssignment) emit(compiler, CODE_NULL); } -static void number(Compiler* compiler, bool allowAssignment) +// A number or string literal. +static void literal(Compiler* compiler, bool allowAssignment) { - int constant = addConstant(compiler, NUM_VAL(compiler->parser->number)); - - // Compile the code to load the constant. - emitShortArg(compiler, CODE_CONSTANT, constant); -} - -// Parses a string literal and adds it to the constant table. -static int stringConstant(Compiler* compiler) -{ - // Define a constant for the literal. - int constant = addConstant(compiler, wrenNewString(compiler->parser->vm, - (char*)compiler->parser->string.data, compiler->parser->string.count)); - - wrenByteBufferClear(compiler->parser->vm, &compiler->parser->string); - - return constant; -} - -static void string(Compiler* compiler, bool allowAssignment) -{ - int constant = stringConstant(compiler); - - // Compile the code to load the constant. - emitShortArg(compiler, CODE_CONSTANT, constant); + emitConstant(compiler); } static void super_(Compiler* compiler, bool allowAssignment) @@ -2409,7 +2381,7 @@ void namedSignature(Compiler* compiler, Signature* signature) // Compiles a method signature for a constructor. void constructorSignature(Compiler* compiler, Signature* signature) { - consume(compiler, TOKEN_NAME, "Expect constructor name after 'this'."); + consume(compiler, TOKEN_NAME, "Expect constructor name after 'construct'."); // Capture the name. *signature = signatureFromToken(compiler, SIG_INITIALIZER); @@ -2500,8 +2472,8 @@ GrammarRule rules[] = /* TOKEN_FIELD */ PREFIX(field), /* TOKEN_STATIC_FIELD */ PREFIX(staticField), /* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL }, - /* TOKEN_NUMBER */ PREFIX(number), - /* TOKEN_STRING */ PREFIX(string), + /* TOKEN_NUMBER */ PREFIX(literal), + /* TOKEN_STRING */ PREFIX(literal), /* TOKEN_LINE */ UNUSED, /* TOKEN_ERROR */ UNUSED, /* TOKEN_EOF */ UNUSED @@ -2591,6 +2563,8 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants, case CODE_LOAD_LOCAL_7: case CODE_LOAD_LOCAL_8: case CODE_CONSTRUCT: + case CODE_FOREIGN_CONSTRUCT: + case CODE_FOREIGN_CLASS: return 0; case CODE_LOAD_LOCAL: @@ -2773,11 +2747,11 @@ static void forStatement(Compiler* compiler) // The space in the variable name ensures it won't collide with a user-defined // variable. expression(compiler); - int seqSlot = defineLocal(compiler, "seq ", 4); + int seqSlot = addLocal(compiler, "seq ", 4); // Create another hidden local for the iterator object. null(compiler, false); - int iterSlot = defineLocal(compiler, "iter ", 5); + int iterSlot = addLocal(compiler, "iter ", 5); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression."); @@ -2805,7 +2779,7 @@ static void forStatement(Compiler* compiler) // Bind the loop variable in its own scope. This ensures we get a fresh // variable each iteration so that closures for it don't all see the same one. pushScope(compiler); - defineLocal(compiler, name, length); + addLocal(compiler, name, length); loopBody(compiler); @@ -2945,7 +2919,8 @@ static void createConstructor(Compiler* compiler, Signature* signature, initCompiler(&methodCompiler, compiler->parser, compiler, false); // Allocate the instance. - emit(&methodCompiler, CODE_CONSTRUCT); + emit(&methodCompiler, compiler->enclosingClass->isForeign + ? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT); // Run its initializer. emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity), @@ -3027,9 +3002,9 @@ static bool method(Compiler* compiler, ClassCompiler* classCompiler, if (isForeign) { // Define a constant for the signature. - int constant = addConstant(compiler, wrenNewString(compiler->parser->vm, - fullSignature, length)); - emitShortArg(compiler, CODE_CONSTANT, constant); + compiler->parser->value = wrenNewString(compiler->parser->vm, + fullSignature, length); + emitConstant(compiler); // We don't need the function we started compiling in the parameter list // any more. @@ -3039,7 +3014,6 @@ static bool method(Compiler* compiler, ClassCompiler* classCompiler, { consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body."); finishBody(&methodCompiler, signature.type == SIG_INITIALIZER); - endCompiler(&methodCompiler, fullSignature, length); } @@ -3066,13 +3040,12 @@ static bool method(Compiler* compiler, ClassCompiler* classCompiler, // Defines a default "new()" constructor on the current class. // -// It just invokes "this new()" on the instance. If a base class defines that, +// It just invokes "init new()" on the instance. If a base class defines that, // it will get invoked. Otherwise, it falls to the default one in Object which // does nothing. static void createDefaultConstructor(Compiler* compiler, int classSlot) { Signature signature = { "new", 3, SIG_INITIALIZER, 0 }; - int initializerSymbol = signatureSymbol(compiler, &signature); signature.type = SIG_METHOD; @@ -3083,17 +3056,16 @@ static void createDefaultConstructor(Compiler* compiler, int classSlot) } // Compiles a class definition. Assumes the "class" token has already been -// consumed. -static void classDefinition(Compiler* compiler) +// consumed (along with a possibly preceding "foreign" token). +static void classDefinition(Compiler* compiler, bool isForeign) { // Create a variable to store the class in. int slot = declareNamedVariable(compiler); // Make a string constant for the name. - int nameConstant = addConstant(compiler, wrenNewString(compiler->parser->vm, - compiler->parser->previous.start, compiler->parser->previous.length)); - - emitShortArg(compiler, CODE_CONSTANT, nameConstant); + compiler->parser->value = wrenNewString(compiler->parser->vm, + compiler->parser->previous.start, compiler->parser->previous.length); + emitConstant(compiler); // Load the superclass (if there is one). if (match(compiler, TOKEN_IS)) @@ -3109,7 +3081,15 @@ static void classDefinition(Compiler* compiler) // Store a placeholder for the number of fields argument. We don't know // the value until we've compiled all the methods to see which fields are // used. - int numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255); + int numFieldsInstruction = -1; + if (isForeign) + { + emit(compiler, CODE_FOREIGN_CLASS); + } + else + { + numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255); + } // Store it in its name. defineVariable(compiler, slot); @@ -3120,6 +3100,7 @@ static void classDefinition(Compiler* compiler) pushScope(compiler); ClassCompiler classCompiler; + classCompiler.isForeign = isForeign; // Set up a symbol table for the class's fields. We'll initially compile // them to slots starting at zero. When the method is bound to the class, the @@ -3155,7 +3136,11 @@ static void classDefinition(Compiler* compiler) } // Update the class with the number of fields. - compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count; + if (!isForeign) + { + compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count; + } + wrenSymbolTableClear(compiler->parser->vm, &fields); compiler->enclosingClass = NULL; @@ -3163,10 +3148,11 @@ static void classDefinition(Compiler* compiler) popScope(compiler); } +// Compiles an "import" statement. static void import(Compiler* compiler) { consume(compiler, TOKEN_STRING, "Expect a string after 'import'."); - int moduleConstant = stringConstant(compiler); + int moduleConstant = addConstant(compiler, compiler->parser->value); // Load the module. emitShortArg(compiler, CODE_LOAD_MODULE, moduleConstant); @@ -3197,6 +3183,7 @@ static void import(Compiler* compiler) } while (match(compiler, TOKEN_COMMA)); } +// Compiles a "var" variable definition statement. static void variableDefinition(Compiler* compiler) { // Grab its name, but don't declare it yet. A (local) variable shouldn't be @@ -3227,23 +3214,25 @@ void definition(Compiler* compiler) { if (match(compiler, TOKEN_CLASS)) { - classDefinition(compiler); - return; + classDefinition(compiler, false); } - - if (match(compiler, TOKEN_IMPORT)) + else if (match(compiler, TOKEN_FOREIGN)) + { + consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'."); + classDefinition(compiler, true); + } + else if (match(compiler, TOKEN_IMPORT)) { import(compiler); - return; } - - if (match(compiler, TOKEN_VAR)) + else if (match(compiler, TOKEN_VAR)) { variableDefinition(compiler); - return; } - - block(compiler); + else + { + block(compiler); + } } ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* sourcePath, @@ -3257,6 +3246,7 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* sourcePath, parser.module = module; parser.sourcePath = AS_STRING(sourcePathValue); parser.source = source; + parser.value = UNDEFINED_VAL; parser.tokenStart = source; parser.currentChar = source; @@ -3274,8 +3264,6 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* sourcePath, parser.printErrors = printErrors; parser.hasError = false; - wrenByteBufferInit(&parser.string); - // Read the first token. nextToken(&parser); @@ -3384,6 +3372,7 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn) void wrenMarkCompiler(WrenVM* vm, Compiler* compiler) { wrenMarkObj(vm, (Obj*)compiler->parser->sourcePath); + wrenMarkValue(vm, compiler->parser->value); // Walk up the parent chain to mark the outer compilers too. The VM only // tracks the innermost one. diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index 62f20232..d0e1d690 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -1291,7 +1291,7 @@ void wrenInitializeCore(WrenVM* vm) PRIMITIVE(vm->objectClass, "!", object_not); PRIMITIVE(vm->objectClass, "==(_)", object_eqeq); PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq); - PRIMITIVE(vm->objectClass, "this new()", return_this); + PRIMITIVE(vm->objectClass, "init new()", return_this); PRIMITIVE(vm->objectClass, "is(_)", object_is); PRIMITIVE(vm->objectClass, "toString", object_toString); PRIMITIVE(vm->objectClass, "type", object_type); diff --git a/src/vm/wren_debug.c b/src/vm/wren_debug.c index cb31f4b8..902a2f2f 100644 --- a/src/vm/wren_debug.c +++ b/src/vm/wren_debug.c @@ -32,10 +32,13 @@ static void dumpObject(Obj* obj) { switch (obj->type) { - case OBJ_CLASS: printf("[class %p]", obj); break; + case OBJ_CLASS: + printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj); + break; case OBJ_CLOSURE: printf("[closure %p]", obj); break; case OBJ_FIBER: printf("[fiber %p]", obj); break; case OBJ_FN: printf("[fn %p]", obj); break; + case OBJ_FOREIGN: printf("[foreign %p]", obj); break; case OBJ_INSTANCE: printf("[instance %p]", obj); break; case OBJ_LIST: printf("[list %p]", obj); break; case OBJ_MAP: printf("[map %p]", obj); break; @@ -269,7 +272,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) break; } - case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break; + case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break; + case CODE_FOREIGN_CONSTRUCT: printf("CODE_FOREIGN_CONSTRUCT\n"); break; case CODE_CLASS: { @@ -278,6 +282,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) break; } + case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break; + case CODE_METHOD_INSTANCE: { int symbol = READ_SHORT(); diff --git a/src/vm/wren_opcodes.h b/src/vm/wren_opcodes.h index 6a48ce45..08ccf83c 100644 --- a/src/vm/wren_opcodes.h +++ b/src/vm/wren_opcodes.h @@ -157,11 +157,22 @@ OPCODE(CLOSURE) // compiler-generated constructor metaclass methods. OPCODE(CONSTRUCT) +// Creates a new instance of a foreign class. +// +// Assumes the class object is in slot zero, and replaces it with the new +// uninitialized instance of that class. This opcode is only emitted by the +// compiler-generated constructor metaclass methods. +OPCODE(FOREIGN_CONSTRUCT) + // Creates a class. Top of stack is the superclass, or `null` if the class // inherits Object. Below that is a string for the name of the class. Byte // [arg] is the number of fields in the class. OPCODE(CLASS) +// Creates a foreign class. Top of stack is the superclass, or `null` if the +// class inherits Object. Below that is a string for the name of the class. +OPCODE(FOREIGN_CLASS) + // Define a method for symbol [arg]. The class receiving the method is popped // off the stack, then the function defining the body is popped. // diff --git a/src/vm/wren_utils.h b/src/vm/wren_utils.h index 33893a76..f81063f3 100644 --- a/src/vm/wren_utils.h +++ b/src/vm/wren_utils.h @@ -6,8 +6,8 @@ // Reusable data structures and other utility functions. -// A simple structure to keep trace of the string length as long as its data -// (including the null-terminator) +// A simple structure to keep track of a string and its length (including the +// null-terminator). typedef struct { char* buffer; uint32_t length; diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index 5fab71c7..bbfc2bbe 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -65,7 +65,15 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) subclass->superclass = superclass; // Include the superclass in the total number of fields. - subclass->numFields += superclass->numFields; + if (subclass->numFields != -1) + { + subclass->numFields += superclass->numFields; + } + else + { + ASSERT(superclass->numFields == 0, + "A foreign class cannot inherit from a class with fields."); + } // Inherit methods from its superclass. for (int i = 0; i < superclass->methods.count; i++) @@ -166,6 +174,16 @@ void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn) wrenAppendCallFrame(vm, fiber, fn, fiber->stack); } +ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size) +{ + ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size); + initObj(vm, &object->obj, OBJ_FOREIGN, classObj); + + // Zero out the bytes. + memset(object->data, 0, size); + return object; +} + ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, const Value* constants, int numConstants, int numUpvalues, int arity, @@ -903,6 +921,15 @@ static void markFn(WrenVM* vm, ObjFn* fn) // TODO: What about the function name? } +static void markForeign(WrenVM* vm, ObjForeign* foreign) +{ + // TODO: Keep track of how much memory the foreign object uses. We can store + // this in each foreign object, but it will balloon the size. We may not want + // that much overhead. One option would be to let the foreign class register + // a C function that returns a size for the object. That way the VM doesn't + // always have to explicitly store it. +} + static void markInstance(WrenVM* vm, ObjInstance* instance) { wrenMarkObj(vm, (Obj*)instance->obj.classObj); @@ -1007,6 +1034,7 @@ void wrenMarkObj(WrenVM* vm, Obj* obj) case OBJ_CLOSURE: markClosure( vm, (ObjClosure*) obj); break; case OBJ_FIBER: markFiber( vm, (ObjFiber*) obj); break; case OBJ_FN: markFn( vm, (ObjFn*) obj); break; + case OBJ_FOREIGN: markForeign( vm, (ObjForeign*) obj); break; case OBJ_INSTANCE: markInstance(vm, (ObjInstance*)obj); break; case OBJ_LIST: markList( vm, (ObjList*) obj); break; case OBJ_MAP: markMap( vm, (ObjMap*) obj); break; @@ -1059,6 +1087,10 @@ void wrenFreeObj(WrenVM* vm, Obj* obj) DEALLOCATE(vm, fn->debug); break; } + + case OBJ_FOREIGN: + // TODO: Call finalizer. + break; case OBJ_LIST: wrenValueBufferClear(vm, &((ObjList*)obj)->elements); diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index 26469e05..f453bcd7 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -51,6 +51,7 @@ #define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure* #define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber* #define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn* +#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign* #define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance* #define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList* #define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap* @@ -74,6 +75,7 @@ #define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure #define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber #define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn +#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign #define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance #define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange #define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString @@ -89,6 +91,7 @@ typedef enum { OBJ_CLOSURE, OBJ_FIBER, OBJ_FN, + OBJ_FOREIGN, OBJ_INSTANCE, OBJ_LIST, OBJ_MAP, @@ -397,6 +400,12 @@ struct sObjClass ObjString* name; }; +typedef struct +{ + Obj obj; + uint8_t data[FLEXIBLE_ARRAY]; +} ObjForeign; + typedef struct { Obj obj; @@ -652,6 +661,8 @@ static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber, frame->ip = wrenGetFrameFunction(frame)->bytecode; } +ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size); + // TODO: The argument list here is getting a bit gratuitous. // Creates a new function object with the given code and constants. The new // function will take over ownership of [bytecode] and [sourceLines]. It will diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index eef3a515..89107058 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -37,6 +37,17 @@ static void* defaultReallocate(void* ptr, size_t newSize) return realloc(ptr, newSize); } +void wrenInitConfiguration(WrenConfiguration* configuration) +{ + configuration->reallocateFn = NULL; + configuration->loadModuleFn = NULL; + configuration->bindForeignMethodFn = NULL; + configuration->bindForeignClassFn = NULL; + configuration->initialHeapSize = 1024 * 1024 * 10; + configuration->minHeapSize = 1024 * 1024; + configuration->heapGrowthPercent = 50; +} + WrenVM* wrenNewVM(WrenConfiguration* configuration) { WrenReallocateFn reallocate = defaultReallocate; @@ -49,32 +60,19 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration) memset(vm, 0, sizeof(WrenVM)); vm->reallocate = reallocate; - vm->bindForeign = configuration->bindForeignMethodFn; + vm->bindForeignMethod = configuration->bindForeignMethodFn; + vm->bindForeignClass = configuration->bindForeignClassFn; vm->loadModule = configuration->loadModuleFn; + vm->nextGC = configuration->initialHeapSize; + vm->minNextGC = configuration->minHeapSize; + // +100 here because the configuration gives us the *additional* size of + // the heap relative to the in-use memory, while heapScalePercent is the + // *total* size of the heap relative to in-use. + vm->heapScalePercent = 100 + configuration->heapGrowthPercent; + wrenSymbolTableInit(&vm->methodNames); - vm->nextGC = 1024 * 1024 * 10; - if (configuration->initialHeapSize != 0) - { - vm->nextGC = configuration->initialHeapSize; - } - - vm->minNextGC = 1024 * 1024; - if (configuration->minHeapSize != 0) - { - vm->minNextGC = configuration->minHeapSize; - } - - vm->heapScalePercent = 150; - if (configuration->heapGrowthPercent != 0) - { - // +100 here because the configuration gives us the *additional* size of - // the heap relative to the in-use memory, while heapScalePercent is the - // *total* size of the heap relative to in-use. - vm->heapScalePercent = 100 + configuration->heapGrowthPercent; - } - ObjString* name = AS_STRING(CONST_STRING(vm, "core")); wrenPushRoot(vm, (Obj*)name); @@ -311,24 +309,21 @@ static WrenForeignMethodFn findForeignMethod(WrenVM* vm, { WrenForeignMethodFn fn; - // Let the host try to find it first. - if (vm->bindForeign != NULL) - { - fn = vm->bindForeign(vm, moduleName, className, isStatic, signature); - if (fn != NULL) return fn; - } - - // Otherwise, try the built-in libraries. + // Bind foreign methods in the core module. if (strcmp(moduleName, "core") == 0) { #if WREN_USE_LIB_IO fn = wrenBindIOForeignMethod(vm, className, signature); - if (fn != NULL) return fn; #endif + + ASSERT(fn != NULL, "Failed to bind core module foreign method."); + return fn; } - // TODO: Report a runtime error on failure to find it. - return NULL; + // For other modules, let the host bind it. + if (vm->bindForeignMethod == NULL) return NULL; + + return vm->bindForeignMethod(vm, moduleName, className, isStatic, signature); } // Defines [methodValue] as a method on [classObj]. @@ -546,18 +541,23 @@ static bool importVariable(WrenVM* vm, Value moduleName, Value variableName, return false; } -// Verifies that [superclass] is a valid object to inherit from. That means it -// must be a class and cannot be the class of any built-in type. +// Verifies that [superclassValue] is a valid object to inherit from. That +// means it must be a class and cannot be the class of any built-in type. // -// If successful, returns null. Otherwise, returns a string for the runtime +// Also validates that it doesn't result in a class with too many fields and +// the other limitations foreign classes have. +// +// If successful, returns `null`. Otherwise, returns a string for the runtime // error message. -static Value validateSuperclass(WrenVM* vm, Value name, - Value superclassValue) +static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue, + int numFields) { // Make sure the superclass is a class. if (!IS_CLASS(superclassValue)) { - return CONST_STRING(vm, "Must inherit from a class."); + return wrenStringFormat(vm, + "Class '@' cannot inherit from a non-class object.", + name); } // Make sure it doesn't inherit from a sealed built-in type. Primitive methods @@ -572,13 +572,125 @@ static Value validateSuperclass(WrenVM* vm, Value name, superclass == vm->rangeClass || superclass == vm->stringClass) { - return wrenStringFormat(vm, "@ cannot inherit from @.", + return wrenStringFormat(vm, + "Class '@' cannot inherit from built-in class '@'.", + name, OBJ_VAL(superclass->name)); + } + + if (superclass->numFields == -1) + { + return wrenStringFormat(vm, + "Class '@' cannot inherit from foreign class '@'.", name, OBJ_VAL(superclass->name)); } + if (numFields == -1 && superclass->numFields > 0) + { + return wrenStringFormat(vm, + "Foreign class '@' may not inherit from a class with fields.", + name); + } + + if (superclass->numFields + numFields > MAX_FIELDS) + { + return wrenStringFormat(vm, + "Class '@' may not have more than 255 fields, including inherited " + "ones.", name); + } + return NULL_VAL; } +static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module) +{ + // TODO: Make this a runtime error? + ASSERT(vm->bindForeignClass != NULL, + "Cannot declare foreign classes without a bindForeignClassFn."); + + WrenForeignClassMethods methods = vm->bindForeignClass( + vm, module->name->value, classObj->name->value); + + Method method; + method.type = METHOD_FOREIGN; + method.fn.foreign = methods.allocate; + + ASSERT(method.fn.foreign != NULL, + "A foreign class must provide an allocate function."); + + int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); + wrenBindMethod(vm, classObj, symbol, method); + + if (methods.finalize != NULL) + { + method.fn.foreign = methods.finalize; + symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "", 10); + wrenBindMethod(vm, classObj, symbol, method); + } +} + +// Creates a new class. +// +// If [numFields] is -1, the class is a foreign class. The name and superclass +// should be on top of the fiber's stack. After calling this, the top of the +// stack will contain either the new class or a string if a runtime error +// occurred. +// +// Returns false if the result is an error. +static bool defineClass(WrenVM* vm, ObjFiber* fiber, int numFields, + ObjModule* module) +{ + // Pull the name and superclass off the stack. + Value name = fiber->stackTop[-2]; + Value superclassValue = fiber->stackTop[-1]; + + // We have two values on the stack and we are going to leave one, so discard + // the other slot. + fiber->stackTop--; + + // Use implicit Object superclass if none given. + ObjClass* superclass = vm->objectClass; + + if (!IS_NULL(superclassValue)) + { + Value error = validateSuperclass(vm, name, superclassValue, numFields); + if (!IS_NULL(error)) + { + fiber->stackTop[-1] = error; + return false; + } + superclass = AS_CLASS(superclassValue); + } + + ObjClass* classObj = wrenNewClass(vm, superclass, numFields, AS_STRING(name)); + fiber->stackTop[-1] = OBJ_VAL(classObj); + + if (numFields == -1) bindForeignClass(vm, classObj, module); + + return true; +} + +static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack) +{ + ObjClass* classObj = AS_CLASS(stack[0]); + ASSERT(classObj->numFields == -1, "Class must be a foreign class."); + + // TODO: Don't look up every time. + int symbol = wrenSymbolTableFind(&vm->methodNames, "", 10); + ASSERT(symbol != -1, "Should have defined symbol."); + + ASSERT(classObj->methods.count > symbol, "Class should have allocator."); + Method* method = &classObj->methods.data[symbol]; + ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign."); + + // Pass the constructor arguments to the allocator as well. + vm->foreignCallSlot = stack; + vm->foreignCallNumArgs = (int)(fiber->stackTop - stack); + + method->fn.foreign(vm); + + // TODO: Check that allocateForeign was called. +} + // The main bytecode interpreter loop. This is where the magic happens. It is // also, as you can imagine, highly performance critical. Returns `true` if the // fiber completed without error. @@ -1022,6 +1134,11 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0])); DISPATCH(); + CASE_CODE(FOREIGN_CONSTRUCT): + ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); + createForeign(vm, fiber, stackStart); + DISPATCH(); + CASE_CODE(CLOSURE): { ObjFn* prototype = AS_FN(fn->constants[READ_SHORT()]); @@ -1057,40 +1174,16 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) CASE_CODE(CLASS): { - Value name = PEEK2(); - ObjClass* superclass = vm->objectClass; - - // Use implicit Object superclass if none given. - if (!IS_NULL(PEEK())) - { - Value error = validateSuperclass(vm, name, PEEK()); - if (!IS_NULL(error)) RUNTIME_ERROR(error); - superclass = AS_CLASS(PEEK()); - } - - int numFields = READ_BYTE(); - - Value classObj = OBJ_VAL(wrenNewClass(vm, superclass, numFields, - AS_STRING(name))); - - // Don't pop the superclass and name off the stack until the subclass is - // done being created, to make sure it doesn't get collected. - DROP(); - DROP(); - - // Now that we know the total number of fields, make sure we don't - // overflow. - if (superclass->numFields + numFields > MAX_FIELDS) - { - RUNTIME_ERROR(wrenStringFormat(vm, - "Class '@' may not have more than 255 fields, including inherited " - "ones.", name)); - } - - PUSH(classObj); + if (!defineClass(vm, fiber, READ_BYTE(), NULL)) RUNTIME_ERROR(PEEK()); DISPATCH(); } - + + CASE_CODE(FOREIGN_CLASS): + { + if (!defineClass(vm, fiber, -1, fn->module)) RUNTIME_ERROR(PEEK()); + DISPATCH(); + } + CASE_CODE(METHOD_INSTANCE): CASE_CODE(METHOD_STATIC): { @@ -1323,6 +1416,20 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value) DEALLOCATE(vm, value); } +void* wrenAllocateForeign(WrenVM* vm, size_t size) +{ + ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); + + // TODO: Validate this. It can fail if the user calls this inside another + // foreign method, or calls one of the return functions. + ObjClass* classObj = AS_CLASS(vm->foreignCallSlot[0]); + + ObjForeign* foreign = wrenNewForeign(vm, classObj, size); + vm->foreignCallSlot[0] = OBJ_VAL(foreign); + + return (void*)foreign->data; +} + // Execute [source] in the context of the core module. static WrenInterpretResult loadIntoCore(WrenVM* vm, const char* source) { @@ -1460,6 +1567,12 @@ void wrenPopRoot(WrenVM* vm) vm->numTempRoots--; } +int wrenGetArgumentCount(WrenVM* vm) +{ + ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); + return vm->foreignCallNumArgs; +} + static void validateForeignArgument(WrenVM* vm, int index) { ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); @@ -1485,6 +1598,15 @@ double wrenGetArgumentDouble(WrenVM* vm, int index) return AS_NUM(*(vm->foreignCallSlot + index)); } +void* wrenGetArgumentForeign(WrenVM* vm, int index) +{ + validateForeignArgument(vm, index); + + if (!IS_FOREIGN(*(vm->foreignCallSlot + index))) return NULL; + + return AS_FOREIGN(*(vm->foreignCallSlot + index))->data; +} + const char* wrenGetArgumentString(WrenVM* vm, int index) { validateForeignArgument(vm, index); diff --git a/src/vm/wren_vm.h b/src/vm/wren_vm.h index 1dfd3427..7e605537 100644 --- a/src/vm/wren_vm.h +++ b/src/vm/wren_vm.h @@ -125,7 +125,10 @@ struct WrenVM int foreignCallNumArgs; // The function used to locate foreign functions. - WrenBindForeignMethodFn bindForeign; + WrenBindForeignMethodFn bindForeignMethod; + + // The function used to locate foreign classes. + WrenBindForeignClassFn bindForeignClass; // The function used to load modules. WrenLoadModuleFn loadModule; diff --git a/test/api/foreign_class.c b/test/api/foreign_class.c new file mode 100644 index 00000000..4e12fc31 --- /dev/null +++ b/test/api/foreign_class.c @@ -0,0 +1,87 @@ +#include +#include + +#include "foreign_class.h" + +static void counterAllocate(WrenVM* vm) +{ + double* value = (double*)wrenAllocateForeign(vm, sizeof(double)); + *value = 0; +} + +static void counterIncrement(WrenVM* vm) +{ + double* value = (double*)wrenGetArgumentForeign(vm, 0); + double increment = wrenGetArgumentDouble(vm, 1); + + *value += increment; +} + +static void counterValue(WrenVM* vm) +{ + double value = *(double*)wrenGetArgumentForeign(vm, 0); + wrenReturnDouble(vm, value); +} + +static void pointAllocate(WrenVM* vm) +{ + double* coordinates = (double*)wrenAllocateForeign(vm, sizeof(double[3])); + + // This gets called by both constructors, so sniff the argument count to see + // which one was invoked. + if (wrenGetArgumentCount(vm) == 1) + { + coordinates[0] = 0.0; + coordinates[1] = 0.0; + coordinates[2] = 0.0; + } + else + { + coordinates[0] = wrenGetArgumentDouble(vm, 1); + coordinates[1] = wrenGetArgumentDouble(vm, 2); + coordinates[2] = wrenGetArgumentDouble(vm, 3); + } +} + +static void pointTranslate(WrenVM* vm) +{ + double* coordinates = (double*)wrenGetArgumentForeign(vm, 0); + coordinates[0] += wrenGetArgumentDouble(vm, 1); + coordinates[1] += wrenGetArgumentDouble(vm, 2); + coordinates[2] += wrenGetArgumentDouble(vm, 3); +} + +static void pointToString(WrenVM* vm) +{ + double* coordinates = (double*)wrenGetArgumentForeign(vm, 0); + char result[100]; + sprintf(result, "(%g, %g, %g)", + coordinates[0], coordinates[1], coordinates[2]); + wrenReturnString(vm, result, (int)strlen(result)); +} + +WrenForeignMethodFn foreignClassBindMethod(const char* signature) +{ + if (strcmp(signature, "Counter.increment(_)") == 0) return counterIncrement; + if (strcmp(signature, "Counter.value") == 0) return counterValue; + if (strcmp(signature, "Point.translate(_,_,_)") == 0) return pointTranslate; + if (strcmp(signature, "Point.toString") == 0) return pointToString; + + return NULL; +} + +void foreignClassBindClass( + const char* className, WrenForeignClassMethods* methods) +{ + if (strcmp(className, "Counter") == 0) + { + methods->allocate = counterAllocate; + return; + } + + if (strcmp(className, "Point") == 0) + { + methods->allocate = pointAllocate; + return; + } +} diff --git a/test/api/foreign_class.h b/test/api/foreign_class.h new file mode 100644 index 00000000..6c075a3f --- /dev/null +++ b/test/api/foreign_class.h @@ -0,0 +1,5 @@ +#include "wren.h" + +WrenForeignMethodFn foreignClassBindMethod(const char* signature); +void foreignClassBindClass( + const char* className, WrenForeignClassMethods* methods); diff --git a/test/api/foreign_class.wren b/test/api/foreign_class.wren new file mode 100644 index 00000000..781988bd --- /dev/null +++ b/test/api/foreign_class.wren @@ -0,0 +1,48 @@ +// Class with a default constructor. +foreign class Counter { + foreign increment(amount) + foreign value +} + +var counter = Counter.new() +IO.print(counter.value) // expect: 0 +counter.increment(3.1) +IO.print(counter.value) // expect: 3.1 +counter.increment(1.2) +IO.print(counter.value) // expect: 4.3 + +// Foreign classes can inherit a class as long as it has no fields. +class PointBase { + inherited() { + IO.print("inherited method") + } +} + +// Class with non-default constructor. +foreign class Point is PointBase { + construct new() { + IO.print("default") + } + + construct new(x, y, z) { + IO.print(x, ", ", y, ", ", z) + } + + foreign translate(x, y, z) + foreign toString +} + +var p = Point.new(1, 2, 3) // expect: 1, 2, 3 +IO.print(p) // expect: (1, 2, 3) +p.translate(3, 4, 5) +IO.print(p) // expect: (4, 6, 8) + +p = Point.new() // expect: default +IO.print(p) // expect: (0, 0, 0) + +p.inherited() // expect: inherited method + +var error = Fiber.new { + class Subclass is Point {} +}.try() +IO.print(error) // expect: Class 'Subclass' cannot inherit from foreign class 'Point'. diff --git a/test/api/get_value/get_value.h b/test/api/get_value/get_value.h deleted file mode 100644 index 8f333fac..00000000 --- a/test/api/get_value/get_value.h +++ /dev/null @@ -1,3 +0,0 @@ -#include "wren.h" - -WrenForeignMethodFn getValueBindForeign(const char* signature); diff --git a/test/api/main.c b/test/api/main.c index 7c261987..117b53c7 100644 --- a/test/api/main.c +++ b/test/api/main.c @@ -5,18 +5,23 @@ #include "vm.h" #include "wren.h" -#include "get_value/get_value.h" -#include "return_bool/return_bool.h" -#include "return_double/return_double.h" -#include "return_null/return_null.h" +#include "foreign_class.h" +#include "returns.h" +#include "value.h" -#define REGISTER_TEST(name, camelCase) \ - if (strcmp(testName, #name) == 0) return camelCase##BindForeign(fullName) +#define REGISTER_METHOD(name, camelCase) \ + if (strcmp(testName, #name) == 0) return camelCase##BindMethod(fullName) + +#define REGISTER_CLASS(name, camelCase) \ + if (strcmp(testName, #name) == 0) \ + { \ + camelCase##BindClass(className, &methods); \ + } // The name of the currently executing API test. const char* testName; -static WrenForeignMethodFn bindForeign( +static WrenForeignMethodFn bindForeignMethod( WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) { @@ -31,10 +36,9 @@ static WrenForeignMethodFn bindForeign( strcat(fullName, "."); strcat(fullName, signature); - REGISTER_TEST(get_value, getValue); - REGISTER_TEST(return_bool, returnBool); - REGISTER_TEST(return_double, returnDouble); - REGISTER_TEST(return_null, returnNull); + REGISTER_METHOD(foreign_class, foreignClass); + REGISTER_METHOD(returns, returns); + REGISTER_METHOD(value, value); fprintf(stderr, "Unknown foreign method '%s' for test '%s'\n", fullName, testName); @@ -42,6 +46,18 @@ static WrenForeignMethodFn bindForeign( return NULL; } +static WrenForeignClassMethods bindForeignClass( + WrenVM* vm, const char* module, const char* className) +{ + WrenForeignClassMethods methods = { NULL, NULL }; + if (strcmp(module, "main") != 0) return methods; + + REGISTER_CLASS(foreign_class, foreignClass); + + return methods; +} + + int main(int argc, const char* argv[]) { if (argc != 2) @@ -52,14 +68,13 @@ int main(int argc, const char* argv[]) testName = argv[1]; - // The test script is at "test/api//.wren". + // 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(testPath, bindForeign); + setForeignCallbacks(bindForeignMethod, bindForeignClass); + runFile(testPath); return 0; } diff --git a/test/api/return_bool/return_bool.c b/test/api/return_bool/return_bool.c deleted file mode 100644 index c52dae4c..00000000 --- a/test/api/return_bool/return_bool.c +++ /dev/null @@ -1,21 +0,0 @@ -#include - -#include "return_bool.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 deleted file mode 100644 index f20d4986..00000000 --- a/test/api/return_bool/return_bool.h +++ /dev/null @@ -1,3 +0,0 @@ -#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 deleted file mode 100644 index 6e33ac8d..00000000 --- a/test/api/return_bool/return_bool.wren +++ /dev/null @@ -1,7 +0,0 @@ -class Api { - foreign static returnTrue - foreign static returnFalse -} - -IO.print(Api.returnTrue) // expect: true -IO.print(Api.returnFalse) // expect: false diff --git a/test/api/return_double/return_double.c b/test/api/return_double/return_double.c deleted file mode 100644 index 83d47153..00000000 --- a/test/api/return_double/return_double.c +++ /dev/null @@ -1,21 +0,0 @@ -#include - -#include "return_double.h" - -static void returnInt(WrenVM* vm) -{ - wrenReturnDouble(vm, 123456); -} - -static void returnFloat(WrenVM* vm) -{ - wrenReturnDouble(vm, 123.456); -} - -WrenForeignMethodFn returnDoubleBindForeign(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 deleted file mode 100644 index 50f49232..00000000 --- a/test/api/return_double/return_double.h +++ /dev/null @@ -1,3 +0,0 @@ -#include "wren.h" - -WrenForeignMethodFn returnDoubleBindForeign(const char* signature); diff --git a/test/api/return_double/return_double.wren b/test/api/return_double/return_double.wren deleted file mode 100644 index ed59e0e9..00000000 --- a/test/api/return_double/return_double.wren +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 41a94d82..00000000 --- a/test/api/return_null/return_null.c +++ /dev/null @@ -1,15 +0,0 @@ -#include - -#include "return_null.h" - -static void implicitNull(WrenVM* vm) -{ - // Do nothing. -} - -WrenForeignMethodFn returnNullBindForeign(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 deleted file mode 100644 index d2afb9c8..00000000 --- a/test/api/return_null/return_null.h +++ /dev/null @@ -1,3 +0,0 @@ -#include "wren.h" - -WrenForeignMethodFn returnNullBindForeign(const char* signature); diff --git a/test/api/return_null/return_null.wren b/test/api/return_null/return_null.wren deleted file mode 100644 index a1164ce3..00000000 --- a/test/api/return_null/return_null.wren +++ /dev/null @@ -1,5 +0,0 @@ -class Api { - foreign static implicitNull -} - -IO.print(Api.implicitNull == null) // expect: true diff --git a/test/api/returns.c b/test/api/returns.c new file mode 100644 index 00000000..0e7d0700 --- /dev/null +++ b/test/api/returns.c @@ -0,0 +1,39 @@ +#include + +#include "returns.h" + +static void implicitNull(WrenVM* vm) +{ + // Do nothing. +} + +static void returnInt(WrenVM* vm) +{ + wrenReturnDouble(vm, 123456); +} + +static void returnFloat(WrenVM* vm) +{ + wrenReturnDouble(vm, 123.456); +} + +static void returnTrue(WrenVM* vm) +{ + wrenReturnBool(vm, true); +} + +static void returnFalse(WrenVM* vm) +{ + wrenReturnBool(vm, false); +} + +WrenForeignMethodFn returnsBindMethod(const char* signature) +{ + if (strcmp(signature, "static Api.implicitNull") == 0) return implicitNull; + if (strcmp(signature, "static Api.returnInt") == 0) return returnInt; + if (strcmp(signature, "static Api.returnFloat") == 0) return returnFloat; + 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/returns.h b/test/api/returns.h new file mode 100644 index 00000000..21fa57cd --- /dev/null +++ b/test/api/returns.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn returnsBindMethod(const char* signature); diff --git a/test/api/returns.wren b/test/api/returns.wren new file mode 100644 index 00000000..f607eef6 --- /dev/null +++ b/test/api/returns.wren @@ -0,0 +1,17 @@ +class Api { + foreign static implicitNull + + foreign static returnInt + foreign static returnFloat + + foreign static returnTrue + foreign static returnFalse +} + +IO.print(Api.implicitNull == null) // expect: true + +IO.print(Api.returnInt) // expect: 123456 +IO.print(Api.returnFloat) // expect: 123.456 + +IO.print(Api.returnTrue) // expect: true +IO.print(Api.returnFalse) // expect: false diff --git a/test/api/get_value/get_value.c b/test/api/value.c similarity index 81% rename from test/api/get_value/get_value.c rename to test/api/value.c index fd02c23a..53022054 100644 --- a/test/api/get_value/get_value.c +++ b/test/api/value.c @@ -1,6 +1,6 @@ #include -#include "get_value.h" +#include "value.h" static WrenValue* value; @@ -15,7 +15,7 @@ static void getValue(WrenVM* vm) wrenReleaseValue(vm, value); } -WrenForeignMethodFn getValueBindForeign(const char* signature) +WrenForeignMethodFn valueBindMethod(const char* signature) { if (strcmp(signature, "static Api.value=(_)") == 0) return setValue; if (strcmp(signature, "static Api.value") == 0) return getValue; diff --git a/test/api/value.h b/test/api/value.h new file mode 100644 index 00000000..dcdea52a --- /dev/null +++ b/test/api/value.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn valueBindMethod(const char* signature); diff --git a/test/api/get_value/get_value.wren b/test/api/value.wren similarity index 100% rename from test/api/get_value/get_value.wren rename to test/api/value.wren diff --git a/test/benchmark/delta_blue.py b/test/benchmark/delta_blue.py index e973de19..09cbc4ce 100644 --- a/test/benchmark/delta_blue.py +++ b/test/benchmark/delta_blue.py @@ -626,7 +626,7 @@ planner = None def delta_blue(): global total start = time.clock() - for i in range(20): + for i in range(40): chain_test(100) projection_test(100) print(total) diff --git a/test/benchmark/delta_blue.wren b/test/benchmark/delta_blue.wren index af57d8a2..74fafafd 100644 --- a/test/benchmark/delta_blue.wren +++ b/test/benchmark/delta_blue.wren @@ -34,17 +34,6 @@ // I've kept it this way to avoid deviating too much from the original // implementation. -// TODO: Support forward declarations of globals. -var REQUIRED = null -var STRONG_REFERRED = null -var PREFERRED = null -var STRONG_DEFAULT = null -var NORMAL = null -var WEAK_DEFAULT = null -var WEAKEST = null - -var ORDERED = null - // Strengths are used to measure the relative importance of constraints. // New strengths may be inserted in the strength hierarchy without // disrupting current constraints. Strengths cannot be created outside @@ -67,15 +56,15 @@ class Strength { } // Compile time computed constants. -REQUIRED = Strength.new(0, "required") -STRONG_REFERRED = Strength.new(1, "strongPreferred") -PREFERRED = Strength.new(2, "preferred") -STRONG_DEFAULT = Strength.new(3, "strongDefault") -NORMAL = Strength.new(4, "normal") -WEAK_DEFAULT = Strength.new(5, "weakDefault") -WEAKEST = Strength.new(6, "weakest") +var REQUIRED = Strength.new(0, "required") +var STRONG_REFERRED = Strength.new(1, "strongPreferred") +var PREFERRED = Strength.new(2, "preferred") +var STRONG_DEFAULT = Strength.new(3, "strongDefault") +var NORMAL = Strength.new(4, "normal") +var WEAK_DEFAULT = Strength.new(5, "weakDefault") +var WEAKEST = Strength.new(6, "weakest") -ORDERED = [ +var ORDERED = [ WEAKEST, WEAK_DEFAULT, NORMAL, STRONG_DEFAULT, PREFERRED, STRONG_REFERRED ] @@ -701,7 +690,7 @@ var projectionTest = Fn.new {|n| } var start = IO.clock -for (i in 0...20) { +for (i in 0...40) { chainTest.call(100) projectionTest.call(100) } diff --git a/test/language/class/field_in_foreign_class.wren b/test/language/class/field_in_foreign_class.wren new file mode 100644 index 00000000..5b7c2c26 --- /dev/null +++ b/test/language/class/field_in_foreign_class.wren @@ -0,0 +1,9 @@ +foreign class Foo { + bar { + // Can't read a field. + IO.print(_bar) // expect error + + // Or write one. + _bar = "value" // expect error + } +} diff --git a/test/language/class/foreign_class_inherit_fields.wren b/test/language/class/foreign_class_inherit_fields.wren new file mode 100644 index 00000000..1be9bbb6 --- /dev/null +++ b/test/language/class/foreign_class_inherit_fields.wren @@ -0,0 +1,7 @@ +class Foo { + method() { + _field = "value" + } +} + +foreign class Bar is Foo {} // expect runtime error: Foreign class 'Bar' may not inherit from a class with fields. diff --git a/test/language/class/missing_class_after_foreign.wren b/test/language/class/missing_class_after_foreign.wren new file mode 100644 index 00000000..b8884850 --- /dev/null +++ b/test/language/class/missing_class_after_foreign.wren @@ -0,0 +1,2 @@ +// Missing "class". +foreign blah A {} // expect error diff --git a/test/language/inheritance/inherit_from_class.wren b/test/language/inheritance/inherit_from_class.wren index d4f8b095..3a256692 100644 --- a/test/language/inheritance/inherit_from_class.wren +++ b/test/language/inheritance/inherit_from_class.wren @@ -1 +1 @@ -class Subclass is Class {} // expect runtime error: Subclass cannot inherit from Class. +class Subclass is Class {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Class'. diff --git a/test/language/inheritance/inherit_from_closure.wren b/test/language/inheritance/inherit_from_closure.wren index 85d79f74..c79e8d01 100644 --- a/test/language/inheritance/inherit_from_closure.wren +++ b/test/language/inheritance/inherit_from_closure.wren @@ -5,4 +5,4 @@ var ClosureType ClosureType = Fn.new { IO.print(a) }.type } -class Subclass is ClosureType {} // expect runtime error: Subclass cannot inherit from Fn. +class Subclass is ClosureType {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Fn'. diff --git a/test/language/inheritance/inherit_from_fiber.wren b/test/language/inheritance/inherit_from_fiber.wren index d620fc17..ab2f59d7 100644 --- a/test/language/inheritance/inherit_from_fiber.wren +++ b/test/language/inheritance/inherit_from_fiber.wren @@ -1 +1 @@ -class Subclass is Fiber {} // expect runtime error: Subclass cannot inherit from Fiber. +class Subclass is Fiber {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Fiber'. diff --git a/test/language/inheritance/inherit_from_fn.wren b/test/language/inheritance/inherit_from_fn.wren index 97a03fda..830bf730 100644 --- a/test/language/inheritance/inherit_from_fn.wren +++ b/test/language/inheritance/inherit_from_fn.wren @@ -1 +1 @@ -class Subclass is Fn {} // expect runtime error: Subclass cannot inherit from Fn. +class Subclass is Fn {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Fn'. diff --git a/test/language/inheritance/inherit_from_list.wren b/test/language/inheritance/inherit_from_list.wren index b4237e05..cba3ca47 100644 --- a/test/language/inheritance/inherit_from_list.wren +++ b/test/language/inheritance/inherit_from_list.wren @@ -1 +1 @@ -class Subclass is List {} // expect runtime error: Subclass cannot inherit from List. +class Subclass is List {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'List'. diff --git a/test/language/inheritance/inherit_from_map.wren b/test/language/inheritance/inherit_from_map.wren index e1c97ba3..d1fe2823 100644 --- a/test/language/inheritance/inherit_from_map.wren +++ b/test/language/inheritance/inherit_from_map.wren @@ -1 +1 @@ -class Subclass is Map {} // expect runtime error: Subclass cannot inherit from Map. +class Subclass is Map {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Map'. diff --git a/test/language/inheritance/inherit_from_nonclass.wren b/test/language/inheritance/inherit_from_nonclass.wren index e849fc89..31fb047f 100644 --- a/test/language/inheritance/inherit_from_nonclass.wren +++ b/test/language/inheritance/inherit_from_nonclass.wren @@ -1 +1 @@ -class Foo is 123 {} // expect runtime error: Must inherit from a class. +class Foo is 123 {} // expect runtime error: Class 'Foo' cannot inherit from a non-class object. diff --git a/test/language/inheritance/inherit_from_range.wren b/test/language/inheritance/inherit_from_range.wren index 88c61a2a..1550df01 100644 --- a/test/language/inheritance/inherit_from_range.wren +++ b/test/language/inheritance/inherit_from_range.wren @@ -1 +1 @@ -class Subclass is Range {} // expect runtime error: Subclass cannot inherit from Range. +class Subclass is Range {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Range'. diff --git a/test/language/inheritance/inherit_from_string.wren b/test/language/inheritance/inherit_from_string.wren index ca9544eb..42285bdf 100644 --- a/test/language/inheritance/inherit_from_string.wren +++ b/test/language/inheritance/inherit_from_string.wren @@ -1 +1 @@ -class Subclass is String {} // expect runtime error: Subclass cannot inherit from String. +class Subclass is String {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'String'. diff --git a/test/language/inheritance/inherit_methods.wren b/test/language/inheritance/inherit_methods.wren index 1361b581..164c8668 100644 --- a/test/language/inheritance/inherit_methods.wren +++ b/test/language/inheritance/inherit_methods.wren @@ -22,5 +22,3 @@ bar.method(1, 2) // expect: bar bar.method(1, 2, 3) // expect: foo bar.method(1, 2, 3, 4) // expect: bar bar.override // expect: bar - -// TODO: Prevent extending built-in types. diff --git a/test/language/number/literals.wren b/test/language/number/literals.wren index 0bcc0a7f..63f36e70 100644 --- a/test/language/number/literals.wren +++ b/test/language/number/literals.wren @@ -6,5 +6,4 @@ IO.print(-0) // expect: -0 IO.print(123.456) // expect: 123.456 IO.print(-0.001) // expect: -0.001 -// TODO: Scientific notation? // TODO: Literals at and beyond numeric limits. diff --git a/test/language/number/scientific_float_missing_exponent.wren b/test/language/number/scientific_float_missing_exponent.wren new file mode 100644 index 00000000..23df0e3c --- /dev/null +++ b/test/language/number/scientific_float_missing_exponent.wren @@ -0,0 +1 @@ +var x = 1.2e // expect error diff --git a/test/language/number/scientific_floating_exponent.wren b/test/language/number/scientific_floating_exponent.wren new file mode 100644 index 00000000..7f44ee3a --- /dev/null +++ b/test/language/number/scientific_floating_exponent.wren @@ -0,0 +1 @@ +var x = 1.2e3.4 // expect error diff --git a/test/language/number/scientific_literals.wren b/test/language/number/scientific_literals.wren new file mode 100644 index 00000000..11024743 --- /dev/null +++ b/test/language/number/scientific_literals.wren @@ -0,0 +1,9 @@ +var x = 2.55e2 + +IO.print(x) // expect: 255 +IO.print(x + 1) // expect: 256 +IO.print(x == 255) // expect: true +IO.print(2.55e-2 is Num) // expect: true +IO.print(x is Num) // expect: true +IO.print(-2.55e2) // expect: -255 +IO.print(-25500e-2) // expect: -255 diff --git a/test/language/number/scientific_missing_exponent.wren b/test/language/number/scientific_missing_exponent.wren new file mode 100644 index 00000000..6bc322c5 --- /dev/null +++ b/test/language/number/scientific_missing_exponent.wren @@ -0,0 +1 @@ +var x = 1e // expect error diff --git a/test/language/number/scientific_missing_fractional_part.wren b/test/language/number/scientific_missing_fractional_part.wren new file mode 100644 index 00000000..96e41ea1 --- /dev/null +++ b/test/language/number/scientific_missing_fractional_part.wren @@ -0,0 +1 @@ +var x = 1.e // expect runtime error: Num does not implement 'e'. \ No newline at end of file diff --git a/test/language/number/scientific_multiple_exponants.wren b/test/language/number/scientific_multiple_exponants.wren new file mode 100644 index 00000000..3d641e3a --- /dev/null +++ b/test/language/number/scientific_multiple_exponants.wren @@ -0,0 +1 @@ +var x = 1.2e3e4 // expect error