diff --git a/Makefile b/Makefile index b1be5105..93e4093e 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,8 @@ docs: watchdocs: @./script/generate_docs.py --watch -# Take the contents of corelib.wren and copy them into src/wren_core.c. -corelib: - @./script/generate_corelib.py +# Take the contents of the scripts under builtin/ and copy them into their +# respective wren_.c source files. +# TODO: Needs dependencies so it knows when to run. +builtin: + @./script/generate_builtins.py diff --git a/builtin/README.md b/builtin/README.md new file mode 100644 index 00000000..ccbb79a2 --- /dev/null +++ b/builtin/README.md @@ -0,0 +1,7 @@ +The Wren scripts in this directory get converted to C string literals and then +inserted into their respective .c files so that the interpreter can load them +directly without having to do any file IO. + +The script that does this copying is `script/generate_builtins.py`. + +You can invoke using `make builtin`. \ No newline at end of file diff --git a/builtin/core.wren b/builtin/core.wren new file mode 100644 index 00000000..4ea15bb6 --- /dev/null +++ b/builtin/core.wren @@ -0,0 +1,11 @@ +class List { + toString { + var result = "[" + for (i in 0...this.count) { + if (i > 0) result = result + ", " + result = result + this[i].toString + } + result = result + "]" + return result + } +} diff --git a/builtin/io.wren b/builtin/io.wren new file mode 100644 index 00000000..7f074d09 --- /dev/null +++ b/builtin/io.wren @@ -0,0 +1,12 @@ +class IO { + static print(obj) { + IO.writeString_(obj.toString) + IO.writeString_("\n") + return obj + } + + static write(obj) { + IO.writeString_(obj.toString) + return obj + } +} diff --git a/corelib.wren b/corelib.wren deleted file mode 100644 index 99cbcba7..00000000 --- a/corelib.wren +++ /dev/null @@ -1,26 +0,0 @@ -// Note: This is converted to a C string literal and inserted into -// src/wren_core.c using make_corelib. -class IO { - static print(obj) { - IO.writeString_(obj.toString) - IO.writeString_("\n") - return obj - } - - static write(obj) { - IO.writeString_(obj.toString) - return obj - } -} - -class List { - toString { - var result = "[" - for (i in 0...this.count) { - if (i > 0) result = result + ", " - result = result + this[i].toString - } - result = result + "]" - return result - } -} diff --git a/include/wren.h b/include/wren.h index 1628b6fd..9aa378d2 100644 --- a/include/wren.h +++ b/include/wren.h @@ -100,11 +100,31 @@ void wrenDefineMethod(WrenVM* vm, const char* className, const char* methodName, int numParams, WrenForeignMethodFn method); +// Defines a static foreign method implemented by the host application. Looks +// for a global class named [className] to bind the method to. If not found, it +// will be created automatically. +// +// Defines a static method on that class named [methodName] accepting +// [numParams] parameters. If a method already exists with that name and arity, +// it will be replaced. When invoked, the method will call [method]. +void wrenDefineStaticMethod(WrenVM* vm, const char* className, + const char* methodName, int numParams, + WrenForeignMethodFn method); + // Reads an numeric argument for a foreign call. This must only be called within // a function provided to [wrenDefineMethod]. Retrieves the argument at [index] // which ranges from 0 to the number of parameters the method expects - 1. double wrenGetArgumentDouble(WrenVM* vm, int index); +// Reads an string argument for a foreign call. This must only be called within +// a function provided to [wrenDefineMethod]. Retrieves the argument at [index] +// which ranges from 0 to the number of parameters the method expects - 1. +// +// The memory for the returned string is owned by Wren. You can inspect it +// while in your foreign function, but cannot keep a pointer to it after the +// function returns, since the garbage collector may reclaim it. +const char* wrenGetArgumentString(WrenVM* vm, int index); + // Provides a numeric return value for a foreign call. This must only be called // within a function provided to [wrenDefineMethod]. Once this is called, the // foreign call is done, and no more arguments can be read or return calls made. diff --git a/script/generate_builtins.py b/script/generate_builtins.py new file mode 100755 index 00000000..16466edb --- /dev/null +++ b/script/generate_builtins.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +import glob +import os.path +import re + +PATTERN = re.compile(r'libSource =\n("(.|[\n])*?);') + +def copy_builtin(filename): + name = os.path.basename(filename) + name = name.split('.')[0] + + with open(filename, "r") as f: + lines = f.readlines() + + wren_source = "" + for line in lines: + line = line.replace('"', "\\\"") + line = line.replace("\n", "\\n\"") + if wren_source: wren_source += "\n" + wren_source += '"' + line + + # re.sub() will unescape escape sequences, but we want them to stay escapes + # in the C string literal. + wren_source = wren_source.replace('\\', '\\\\') + + constant = "libSource =\n" + wren_source + ";" + + with open("src/wren_" + name + ".c", "r") as f: + c_source = f.read() + + c_source = PATTERN.sub(constant, c_source) + + with open("src/wren_" + name + ".c", "w") as f: + f.write(c_source) + + print name + + +for f in glob.iglob("builtin/*.wren"): + copy_builtin(f) diff --git a/script/generate_corelib.py b/script/generate_corelib.py deleted file mode 100755 index f0073641..00000000 --- a/script/generate_corelib.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -import re - -with open("corelib.wren", "r") as f: - lines = f.readlines() - -# Remove the comment from the top. -lines.pop(0) -lines.pop(0) - -corelib = "" -for line in lines: - line = line.replace('"', "\\\"") - line = line.replace("\n", "\\n\"") - if corelib: corelib += "\n" - corelib += '"' + line - -with open("src/wren_core.c", "r") as f: - wren_core = f.read() - -# re.sub() will unescape escape sequences, but we want them to stay as escapes -# in the C string literal. -corelib = corelib.replace('\\', '\\\\') - -corelib = "coreLibSource =\n" + corelib + ";" - -PATTERN = re.compile(r'coreLibSource =\n("(.|[\n])*?);') -wren_core = PATTERN.sub(corelib, wren_core) - -with open("src/wren_core.c", "w") as f: - f.write(wren_core) diff --git a/src/wren_core.c b/src/wren_core.c index cef0670a..4afaa867 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -40,22 +40,8 @@ return PRIM_ERROR; \ } while (0); -// This string literal is generated automatically from corelib.wren using -// make_corelib. Do not edit here. -const char* coreLibSource = -"class IO {\n" -" static print(obj) {\n" -" IO.writeString_(obj.toString)\n" -" IO.writeString_(\"\n\")\n" -" return obj\n" -" }\n" -"\n" -" static write(obj) {\n" -" IO.writeString_(obj.toString)\n" -" return obj\n" -" }\n" -"}\n" -"\n" +// This string literal is generated automatically from core. Do not edit. +static const char* libSource = "class List {\n" " toString {\n" " var result = \"[\"\n" @@ -68,7 +54,6 @@ const char* coreLibSource = " }\n" "}\n"; - // Validates that the given argument in [args] is a Num. Returns true if it is. // If not, reports an error and returns false. static bool validateNum(WrenVM* vm, Value* args, int index, const char* argName) @@ -746,13 +731,6 @@ DEF_NATIVE(string_subscript) RETURN_VAL(value); } -DEF_NATIVE(io_writeString) -{ - if (!validateString(vm, args, 1, "Argument")) return PRIM_ERROR; - wrenPrintValue(args[1]); - RETURN_NULL; -} - DEF_NATIVE(os_clock) { double time = (double)clock() / CLOCKS_PER_SEC; @@ -924,7 +902,7 @@ void wrenInitializeCore(WrenVM* vm) ObjClass* osClass = defineClass(vm, "OS"); NATIVE(osClass->metaclass, "clock", os_clock); - wrenInterpret(vm, "Wren core library", coreLibSource); + wrenInterpret(vm, "Wren core library", libSource); vm->listClass = AS_CLASS(findGlobal(vm, "List")); NATIVE(vm->listClass, "add ", list_add); @@ -941,7 +919,4 @@ void wrenInitializeCore(WrenVM* vm) // IEEE 754 even though they have different bit representations. NATIVE(vm->numClass, "== ", num_eqeq); NATIVE(vm->numClass, "!= ", num_bangeq); - - ObjClass* ioClass = AS_CLASS(findGlobal(vm, "IO")); - NATIVE(ioClass->metaclass, "writeString_ ", io_writeString); } diff --git a/src/wren_io.c b/src/wren_io.c new file mode 100644 index 00000000..e570d0e8 --- /dev/null +++ b/src/wren_io.c @@ -0,0 +1,31 @@ +#include + +#include "wren_io.h" + +// This string literal is generated automatically from io.wren. Do not edit. +static const char* libSource = +"class IO {\n" +" static print(obj) {\n" +" IO.writeString_(obj.toString)\n" +" IO.writeString_(\"\n\")\n" +" return obj\n" +" }\n" +"\n" +" static write(obj) {\n" +" IO.writeString_(obj.toString)\n" +" return obj\n" +" }\n" +"}\n"; + +static void writeString(WrenVM* vm) +{ + const char* s = wrenGetArgumentString(vm, 1); + // TODO: Check for null. + printf("%s", s); +} + +void wrenLoadIOLibrary(WrenVM* vm) +{ + wrenInterpret(vm, "Wren IO library", libSource); + wrenDefineStaticMethod(vm, "IO", "writeString_", 1, writeString); +} diff --git a/src/wren_io.h b/src/wren_io.h new file mode 100644 index 00000000..796f7cdd --- /dev/null +++ b/src/wren_io.h @@ -0,0 +1,11 @@ +#ifndef wren_io_h +#define wren_io_h + +#include "wren.h" + +// This module defines the IO class and its associated methods. They are +// implemented using the C standard library. + +void wrenLoadIOLibrary(WrenVM* vm); + +#endif diff --git a/src/wren_vm.c b/src/wren_vm.c index 810e7eba..fbf482eb 100644 --- a/src/wren_vm.c +++ b/src/wren_vm.c @@ -9,6 +9,8 @@ // TODO: This is used for printing the stack trace on an error. This should be // behind a flag so that you can use Wren with all debugging info stripped out. #include "wren_debug.h" +// TODO: Put this behind a flag so users can disable the IO library. +#include "wren_io.h" #include "wren_vm.h" #if WREN_TRACE_MEMORY || WREN_TRACE_GC @@ -76,6 +78,8 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration) vm->foreignCallNumArgs = 0; wrenInitializeCore(vm); + wrenLoadIOLibrary(vm); + return vm; } @@ -288,9 +292,7 @@ static void callForeign(WrenVM* vm, ObjFiber* fiber, WrenForeignMethodFn foreign, int numArgs) { vm->foreignCallSlot = &fiber->stack[fiber->stackSize - numArgs]; - - // Don't include the receiver. - vm->foreignCallNumArgs = numArgs - 1; + vm->foreignCallNumArgs = numArgs; foreign(vm); @@ -1061,9 +1063,9 @@ void wrenUnpinObj(WrenVM* vm) vm->pinned = vm->pinned->previous; } -void wrenDefineMethod(WrenVM* vm, const char* className, - const char* methodName, int numParams, - WrenForeignMethodFn methodFn) +static void defineMethod(WrenVM* vm, const char* className, + const char* methodName, int numParams, + WrenForeignMethodFn methodFn, bool isStatic) { ASSERT(className != NULL, "Must provide class name."); @@ -1119,18 +1121,44 @@ void wrenDefineMethod(WrenVM* vm, const char* className, Method method; method.type = METHOD_FOREIGN; method.foreign = methodFn; + + if (isStatic) classObj = classObj->metaclass; + wrenBindMethod(vm, classObj, methodSymbol, method); } +void wrenDefineMethod(WrenVM* vm, const char* className, + const char* methodName, int numParams, + WrenForeignMethodFn methodFn) +{ + defineMethod(vm, className, methodName, numParams, methodFn, false); +} + +void wrenDefineStaticMethod(WrenVM* vm, const char* className, + const char* methodName, int numParams, + WrenForeignMethodFn methodFn) +{ + defineMethod(vm, className, methodName, numParams, methodFn, true); +} + double wrenGetArgumentDouble(WrenVM* vm, int index) { ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); ASSERT(index >= 0, "index cannot be negative."); ASSERT(index < vm->foreignCallNumArgs, "Not that many arguments."); - // + 1 to shift past the receiver. // TODO: Check actual value type first. - return AS_NUM(*(vm->foreignCallSlot + index + 1)); + return AS_NUM(*(vm->foreignCallSlot + index)); +} + +const char* wrenGetArgumentString(WrenVM* vm, int index) +{ + ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); + ASSERT(index >= 0, "index cannot be negative."); + ASSERT(index < vm->foreignCallNumArgs, "Not that many arguments."); + + // TODO: Check actual value type first. + return AS_CSTRING(*(vm->foreignCallSlot + index)); } void wrenReturnDouble(WrenVM* vm, double value) diff --git a/wren.xcodeproj/project.pbxproj b/wren.xcodeproj/project.pbxproj index 658fd5d9..8d82aec9 100644 --- a/wren.xcodeproj/project.pbxproj +++ b/wren.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 29AB1F3218170104004B501E /* wren_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = 29AB1F3018170104004B501E /* wren_compiler.c */; }; 29B0C61C187524A500354372 /* wren_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 29B0C61B187524A500354372 /* wren_utils.c */; }; 29CEDD5718495A630074C8B7 /* wren_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = 29CEDD5618495A630074C8B7 /* wren_debug.c */; }; + 29EC5BBA18A024DA00BA4D1C /* wren_io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29EC5BB918A024DA00BA4D1C /* wren_io.c */; }; 29FA7298181D91020089013C /* wren_core.c in Sources */ = {isa = PBXBuildFile; fileRef = 29FA7297181D91020089013C /* wren_core.c */; }; /* End PBXBuildFile section */ @@ -43,6 +44,8 @@ 29B0C61D187524BC00354372 /* wren_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_utils.h; path = src/wren_utils.h; sourceTree = SOURCE_ROOT; }; 29BD8159184CCEE700FA45E9 /* wren_debug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_debug.h; path = src/wren_debug.h; sourceTree = SOURCE_ROOT; }; 29CEDD5618495A630074C8B7 /* wren_debug.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_debug.c; path = src/wren_debug.c; sourceTree = SOURCE_ROOT; }; + 29EC5BB818A024A400BA4D1C /* wren_io.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_io.h; path = src/wren_io.h; sourceTree = SOURCE_ROOT; }; + 29EC5BB918A024DA00BA4D1C /* wren_io.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_io.c; path = src/wren_io.c; sourceTree = SOURCE_ROOT; }; 29FA7296181D90F30089013C /* wren_core.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_core.h; path = src/wren_core.h; sourceTree = SOURCE_ROOT; }; 29FA7297181D91020089013C /* wren_core.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_core.c; path = src/wren_core.c; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -100,6 +103,8 @@ 292A45D41838566F00C34813 /* wren_value.c */, 29AB1F2D1816FA5B004B501E /* wren_vm.h */, 29AB1F2E1816FA66004B501E /* wren_vm.c */, + 29EC5BB818A024A400BA4D1C /* wren_io.h */, + 29EC5BB918A024DA00BA4D1C /* wren_io.c */, ); name = src; path = wren; @@ -161,6 +166,7 @@ 29AB1F291816E49C004B501E /* main.c in Sources */, 29FA7298181D91020089013C /* wren_core.c in Sources */, 29AB1F3218170104004B501E /* wren_compiler.c in Sources */, + 29EC5BBA18A024DA00BA4D1C /* wren_io.c in Sources */, 29AB1F2F1816FA66004B501E /* wren_vm.c in Sources */, 29B0C61C187524A500354372 /* wren_utils.c in Sources */, );