initial commit
This commit is contained in:
		
						commit
						b6c60365ab
					
				
					 67 changed files with 17447 additions and 0 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| /build | ||||
							
								
								
									
										9
									
								
								CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| cmake_minimum_required(VERSION 3.15) | ||||
| 
 | ||||
| project(scripted-engine) | ||||
| 
 | ||||
| set(CMAKE_CXX_STANDARD 14) | ||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||||
| set(CMAKE_CXX_EXTENSIONS OFF) | ||||
| 
 | ||||
| add_subdirectory(src) | ||||
							
								
								
									
										5
									
								
								src/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| add_subdirectory(engine) | ||||
| add_subdirectory(logic) | ||||
| 
 | ||||
| add_executable(sc-eng main.cpp) | ||||
| target_link_libraries(sc-eng engine logic) | ||||
							
								
								
									
										6
									
								
								src/engine/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/engine/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| set(MODULE engine) | ||||
| 
 | ||||
| add_library(${MODULE} | ||||
|     engine.cpp engine.hpp) | ||||
| 
 | ||||
| target_include_directories(${MODULE} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) | ||||
							
								
								
									
										8
									
								
								src/engine/engine.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/engine/engine.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| #include "engine.hpp" | ||||
| 
 | ||||
| Engine::Engine() { | ||||
| } | ||||
| 
 | ||||
| int Engine::get_info() { | ||||
|     return 42; | ||||
| } | ||||
							
								
								
									
										8
									
								
								src/engine/engine.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/engine/engine.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| #pragma once | ||||
| 
 | ||||
| class Engine { | ||||
| public: | ||||
|     Engine(); | ||||
| 
 | ||||
|     int get_info(); | ||||
| }; | ||||
							
								
								
									
										8
									
								
								src/logic/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/logic/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| add_subdirectory(wren) | ||||
| 
 | ||||
| set(MODULE logic) | ||||
| 
 | ||||
| add_library(${MODULE} | ||||
|     logic.cpp logic.hpp) | ||||
| target_link_libraries(${MODULE} wren) | ||||
| target_link_libraries(${MODULE} engine) | ||||
							
								
								
									
										74
									
								
								src/logic/logic.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/logic/logic.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| #include "logic.hpp" | ||||
| 
 | ||||
| #include "engine.hpp" | ||||
| #include "wren/vm/wren_vm.h" | ||||
| 
 | ||||
| #include <cassert> | ||||
| #include <functional> | ||||
| #include <iostream> | ||||
| 
 | ||||
| void writeOutput(WrenVM *vm, const char *text) { | ||||
|     std::cout << "wren output:" << text << "\n"; | ||||
| } | ||||
| 
 | ||||
| void errorOutput(WrenVM *vm, WrenErrorType type, const char *module, int line, const char *message) { | ||||
|     std::cerr << "wren error [module \"" << module << "\", line " << line << "]: " << message << "\n"; | ||||
| } | ||||
| 
 | ||||
| Logic::Logic(Engine *engine) { | ||||
|     WrenConfiguration wrenConfig; | ||||
|     wrenInitConfiguration(&wrenConfig); | ||||
| 
 | ||||
|     // Setup user-defined data such as pointer to "global" objects.
 | ||||
|     _bundleData.engine = engine; | ||||
|     wrenConfig.userData = &_bundleData; | ||||
| 
 | ||||
|     wrenConfig.bindForeignMethodFn = &Logic::bindForeignMethod; | ||||
|     wrenConfig.writeFn = &writeOutput; | ||||
|     wrenConfig.errorFn = &errorOutput; | ||||
| 
 | ||||
|     _wrenVm = wrenNewVM(&wrenConfig); | ||||
|     WrenInterpretResult result = wrenInterpret(_wrenVm, "scripted-engine", "System.print(\"I am running in a VM!\")"); | ||||
|     assert(result == WREN_RESULT_SUCCESS); | ||||
| } | ||||
| 
 | ||||
| Logic::~Logic() { | ||||
|     wrenFreeVM(_wrenVm); | ||||
| } | ||||
| 
 | ||||
| void get_info(WrenVM *vm) { | ||||
|     // Retrieve "global" objects defined in userData
 | ||||
|     BundleData *bundleData = reinterpret_cast<BundleData *>(vm->config.userData); | ||||
|     Engine *engine = bundleData->engine; | ||||
|     assert(engine); | ||||
| 
 | ||||
|     wrenEnsureSlots(vm, 1); | ||||
|     wrenSetSlotDouble(vm, 0, engine->get_info()); | ||||
| } | ||||
| 
 | ||||
| WrenForeignMethodFn | ||||
| Logic::bindForeignMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature) { | ||||
|     if (strcmp(module, "scripted-engine") == 0) { | ||||
|         if (strcmp(className, "Engine") == 0) { | ||||
|             if (isStatic && strcmp(signature, "get_info()") == 0) { | ||||
|                 return get_info; | ||||
|             } | ||||
|             // Other foreign methods on Math...
 | ||||
|         } | ||||
|         // Other classes in main...
 | ||||
|     } | ||||
|     // Other modules...
 | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| void Logic::interpret() { | ||||
|     assert(_wrenVm); | ||||
|     WrenInterpretResult result = wrenInterpret(_wrenVm, "scripted-engine", "class Engine {\
 | ||||
|                                                foreign static get_info())\ | ||||
| }\ | ||||
| Engine.get_info()"); | ||||
|     assert(result == WREN_RESULT_SUCCESS); | ||||
| } | ||||
| 
 | ||||
| void Logic::add_item() { | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/logic/logic.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/logic/logic.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "wren/include/wren.hpp" | ||||
| 
 | ||||
| class Engine; | ||||
| 
 | ||||
| struct BundleData { | ||||
|     Engine *engine = nullptr; | ||||
| }; | ||||
| 
 | ||||
| class Logic { | ||||
| public: | ||||
|     Logic(Engine *engine); | ||||
|     ~Logic(); | ||||
| 
 | ||||
|     static WrenForeignMethodFn | ||||
|     bindForeignMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature); | ||||
|     void interpret(); | ||||
| 
 | ||||
|     void add_item(); | ||||
| 
 | ||||
| private: | ||||
|     BundleData _bundleData; | ||||
|     WrenVM *_wrenVm = nullptr; | ||||
| }; | ||||
							
								
								
									
										13
									
								
								src/logic/wren/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/logic/wren/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| set(MODULE wren) | ||||
| 
 | ||||
| file(GLOB OPT_FILES ${CMAKE_CURRENT_SOURCE_DIR}/optional/*.c) | ||||
| file(GLOB VM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/vm/*.c) | ||||
| 
 | ||||
| add_library(${MODULE} ${VM_FILES} ${OPT_FILES}) | ||||
| target_include_directories(${MODULE} | ||||
|     PUBLIC | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/include | ||||
|     PRIVATE | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/optional | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/vm | ||||
|     ) | ||||
							
								
								
									
										24
									
								
								src/logic/wren/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/logic/wren/README.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| This contains the Wren source code. It is organized like so: | ||||
| 
 | ||||
| *   `optional`: the Wren and C source code for the optional modules. These are | ||||
|     built in to the VM and can be used even when you embed the VM in your own | ||||
|     application. But they are also optional and can be compiled out by setting | ||||
|     defines. | ||||
| 
 | ||||
| *   `cli`: the source code for the command line interface. This is a custom | ||||
|     executable that embeds the VM in itself. Code here handles reading | ||||
|     command-line, running the REPL, loading modules from disc, etc. | ||||
| 
 | ||||
| *   `include`: the public header directory for the VM. If you are embedding the | ||||
|     VM in your own application, you will add this to your include path. | ||||
| 
 | ||||
| *   `module`: the source code for the built-in modules that come with the CLI. | ||||
|     These modules are written in a mixture of C and Wren and generally use | ||||
|     [libuv][] to implement their underlying functionality. | ||||
| 
 | ||||
| *   `vm`: the source code for the Wren VM itself. Unlike code in `cli` and | ||||
|     `module`, this has no dependencies on libuv. If you are embedding the VM in | ||||
|     your own application from source, you will compile the files here into your | ||||
|     app. | ||||
| 
 | ||||
| [libuv]: http://libuv.org/ | ||||
							
								
								
									
										40
									
								
								src/logic/wren/cli/main.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/logic/wren/cli/main.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "os.h" | ||||
| #include "vm.h" | ||||
| #include "wren.h" | ||||
| 
 | ||||
| int main(int argc, const char* argv[]) | ||||
| { | ||||
|   if (argc == 2 && strcmp(argv[1], "--help") == 0) | ||||
|   { | ||||
|     printf("Usage: wren [file] [arguments...]\n"); | ||||
|     printf("  --help  Show command line usage\n"); | ||||
|     return 0; | ||||
|   } | ||||
|    | ||||
|   if (argc == 2 && strcmp(argv[1], "--version") == 0) | ||||
|   { | ||||
|     printf("wren %s\n", WREN_VERSION_STRING); | ||||
|     return 0; | ||||
|   } | ||||
|    | ||||
|   osSetArguments(argc, argv); | ||||
| 
 | ||||
|   WrenInterpretResult result; | ||||
|   if (argc == 1) | ||||
|   { | ||||
|     result = runRepl(); | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     result = runFile(argv[1]); | ||||
|   } | ||||
| 
 | ||||
|   // Exit with an error code if the script failed.
 | ||||
|   if (result == WREN_RESULT_COMPILE_ERROR) return 65; // EX_DATAERR.
 | ||||
|   if (result == WREN_RESULT_RUNTIME_ERROR) return 70; // EX_SOFTWARE.
 | ||||
|    | ||||
|   return getExitCode(); | ||||
| } | ||||
							
								
								
									
										278
									
								
								src/logic/wren/cli/modules.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								src/logic/wren/cli/modules.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,278 @@ | |||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "modules.h" | ||||
| 
 | ||||
| #include "io.wren.inc" | ||||
| #include "os.wren.inc" | ||||
| #include "repl.wren.inc" | ||||
| #include "scheduler.wren.inc" | ||||
| #include "timer.wren.inc" | ||||
| 
 | ||||
| extern void directoryList(WrenVM* vm); | ||||
| extern void fileAllocate(WrenVM* vm); | ||||
| extern void fileFinalize(void* data); | ||||
| extern void fileDelete(WrenVM* vm); | ||||
| extern void fileOpen(WrenVM* vm); | ||||
| extern void fileSizePath(WrenVM* vm); | ||||
| extern void fileClose(WrenVM* vm); | ||||
| extern void fileDescriptor(WrenVM* vm); | ||||
| extern void fileReadBytes(WrenVM* vm); | ||||
| extern void fileRealPath(WrenVM* vm); | ||||
| extern void fileSize(WrenVM* vm); | ||||
| extern void fileStat(WrenVM* vm); | ||||
| extern void fileWriteBytes(WrenVM* vm); | ||||
| extern void platformIsPosix(WrenVM* vm); | ||||
| extern void platformName(WrenVM* vm); | ||||
| extern void processAllArguments(WrenVM* vm); | ||||
| extern void statPath(WrenVM* vm); | ||||
| extern void statBlockCount(WrenVM* vm); | ||||
| extern void statBlockSize(WrenVM* vm); | ||||
| extern void statDevice(WrenVM* vm); | ||||
| extern void statGroup(WrenVM* vm); | ||||
| extern void statInode(WrenVM* vm); | ||||
| extern void statLinkCount(WrenVM* vm); | ||||
| extern void statMode(WrenVM* vm); | ||||
| extern void statSize(WrenVM* vm); | ||||
| extern void statSpecialDevice(WrenVM* vm); | ||||
| extern void statUser(WrenVM* vm); | ||||
| extern void statIsDirectory(WrenVM* vm); | ||||
| extern void statIsFile(WrenVM* vm); | ||||
| extern void stdinIsRaw(WrenVM* vm); | ||||
| extern void stdinIsRawSet(WrenVM* vm); | ||||
| extern void stdinIsTerminal(WrenVM* vm); | ||||
| extern void stdinReadStart(WrenVM* vm); | ||||
| extern void stdinReadStop(WrenVM* vm); | ||||
| extern void stdoutFlush(WrenVM* vm); | ||||
| extern void schedulerCaptureMethods(WrenVM* vm); | ||||
| extern void timerStartTimer(WrenVM* vm); | ||||
| 
 | ||||
| // The maximum number of foreign methods a single class defines. Ideally, we
 | ||||
| // would use variable-length arrays for each class in the table below, but
 | ||||
| // C++98 doesn't have any easy syntax for nested global static data, so we
 | ||||
| // just use worst-case fixed-size arrays instead.
 | ||||
| //
 | ||||
| // If you add a new method to the longest class below, make sure to bump this.
 | ||||
| // Note that it also includes an extra slot for the sentinel value indicating
 | ||||
| // the end of the list.
 | ||||
| #define MAX_METHODS_PER_CLASS 14 | ||||
| 
 | ||||
| // The maximum number of foreign classes a single built-in module defines.
 | ||||
| //
 | ||||
| // If you add a new class to the largest module below, make sure to bump this.
 | ||||
| // Note that it also includes an extra slot for the sentinel value indicating
 | ||||
| // the end of the list.
 | ||||
| #define MAX_CLASSES_PER_MODULE 6 | ||||
| 
 | ||||
| // Describes one foreign method in a class.
 | ||||
| typedef struct | ||||
| { | ||||
|   bool isStatic; | ||||
|   const char* signature; | ||||
|   WrenForeignMethodFn method; | ||||
| } MethodRegistry; | ||||
| 
 | ||||
| // Describes one class in a built-in module.
 | ||||
| typedef struct | ||||
| { | ||||
|   const char* name; | ||||
| 
 | ||||
|   MethodRegistry methods[MAX_METHODS_PER_CLASS]; | ||||
| } ClassRegistry; | ||||
| 
 | ||||
| // Describes one built-in module.
 | ||||
| typedef struct | ||||
| { | ||||
|   // The name of the module.
 | ||||
|   const char* name; | ||||
| 
 | ||||
|   // Pointer to the string containing the source code of the module. We use a
 | ||||
|   // pointer here because the string variable itself is not a constant
 | ||||
|   // expression so can't be used in the initializer below.
 | ||||
|   const char **source; | ||||
| 
 | ||||
|   ClassRegistry classes[MAX_CLASSES_PER_MODULE]; | ||||
| } ModuleRegistry; | ||||
| 
 | ||||
| // To locate foreign classes and modules, we build a big directory for them in
 | ||||
| // static data. The nested collection initializer syntax gets pretty noisy, so
 | ||||
| // define a couple of macros to make it easier.
 | ||||
| #define SENTINEL_METHOD { false, NULL, NULL } | ||||
| #define SENTINEL_CLASS { NULL, { SENTINEL_METHOD } } | ||||
| #define SENTINEL_MODULE {NULL, NULL, { SENTINEL_CLASS } } | ||||
| 
 | ||||
| #define MODULE(name) { #name, &name##ModuleSource, { | ||||
| #define END_MODULE SENTINEL_CLASS } }, | ||||
| 
 | ||||
| #define CLASS(name) { #name, { | ||||
| #define END_CLASS SENTINEL_METHOD } }, | ||||
| 
 | ||||
| #define METHOD(signature, fn) { false, signature, fn }, | ||||
| #define STATIC_METHOD(signature, fn) { true, signature, fn }, | ||||
| #define FINALIZER(fn) { true, "<finalize>", (WrenForeignMethodFn)fn }, | ||||
| 
 | ||||
| // The array of built-in modules.
 | ||||
| static ModuleRegistry modules[] = | ||||
| { | ||||
|   MODULE(io) | ||||
|     CLASS(Directory) | ||||
|       STATIC_METHOD("list_(_,_)", directoryList) | ||||
|     END_CLASS | ||||
|     CLASS(File) | ||||
|       STATIC_METHOD("<allocate>", fileAllocate) | ||||
|       FINALIZER(fileFinalize) | ||||
|       STATIC_METHOD("delete_(_,_)", fileDelete) | ||||
|       STATIC_METHOD("open_(_,_,_)", fileOpen) | ||||
|       STATIC_METHOD("realPath_(_,_)", fileRealPath) | ||||
|       STATIC_METHOD("sizePath_(_,_)", fileSizePath) | ||||
|       METHOD("close_(_)", fileClose) | ||||
|       METHOD("descriptor", fileDescriptor) | ||||
|       METHOD("readBytes_(_,_,_)", fileReadBytes) | ||||
|       METHOD("size_(_)", fileSize) | ||||
|       METHOD("stat_(_)", fileStat) | ||||
|       METHOD("writeBytes_(_,_,_)", fileWriteBytes) | ||||
|     END_CLASS | ||||
|     CLASS(Stat) | ||||
|       STATIC_METHOD("path_(_,_)", statPath) | ||||
|       METHOD("blockCount", statBlockCount) | ||||
|       METHOD("blockSize", statBlockSize) | ||||
|       METHOD("device", statDevice) | ||||
|       METHOD("group", statGroup) | ||||
|       METHOD("inode", statInode) | ||||
|       METHOD("linkCount", statLinkCount) | ||||
|       METHOD("mode", statMode) | ||||
|       METHOD("size", statSize) | ||||
|       METHOD("specialDevice", statSpecialDevice) | ||||
|       METHOD("user", statUser) | ||||
|       METHOD("isDirectory", statIsDirectory) | ||||
|       METHOD("isFile", statIsFile) | ||||
|     END_CLASS | ||||
|     CLASS(Stdin) | ||||
|       STATIC_METHOD("isRaw", stdinIsRaw) | ||||
|       STATIC_METHOD("isRaw=(_)", stdinIsRawSet) | ||||
|       STATIC_METHOD("isTerminal", stdinIsTerminal) | ||||
|       STATIC_METHOD("readStart_()", stdinReadStart) | ||||
|       STATIC_METHOD("readStop_()", stdinReadStop) | ||||
|     END_CLASS | ||||
|     CLASS(Stdout) | ||||
|       STATIC_METHOD("flush()", stdoutFlush) | ||||
|     END_CLASS | ||||
|   END_MODULE | ||||
|   MODULE(os) | ||||
|     CLASS(Platform) | ||||
|       STATIC_METHOD("isPosix", platformIsPosix) | ||||
|       STATIC_METHOD("name", platformName) | ||||
|     END_CLASS | ||||
|     CLASS(Process) | ||||
|       STATIC_METHOD("allArguments", processAllArguments) | ||||
|     END_CLASS | ||||
|   END_MODULE | ||||
|   MODULE(repl) | ||||
|   END_MODULE | ||||
|   MODULE(scheduler) | ||||
|     CLASS(Scheduler) | ||||
|       STATIC_METHOD("captureMethods_()", schedulerCaptureMethods) | ||||
|     END_CLASS | ||||
|   END_MODULE | ||||
|   MODULE(timer) | ||||
|     CLASS(Timer) | ||||
|       STATIC_METHOD("startTimer_(_,_)", timerStartTimer) | ||||
|     END_CLASS | ||||
|   END_MODULE | ||||
| 
 | ||||
|   SENTINEL_MODULE | ||||
| }; | ||||
| 
 | ||||
| #undef SENTINEL_METHOD | ||||
| #undef SENTINEL_CLASS | ||||
| #undef SENTINEL_MODULE | ||||
| #undef MODULE | ||||
| #undef END_MODULE | ||||
| #undef CLASS | ||||
| #undef END_CLASS | ||||
| #undef METHOD | ||||
| #undef STATIC_METHOD | ||||
| #undef FINALIZER | ||||
| 
 | ||||
| // Looks for a built-in module with [name].
 | ||||
| //
 | ||||
| // Returns the BuildInModule for it or NULL if not found.
 | ||||
| static ModuleRegistry* findModule(const char* name) | ||||
| { | ||||
|   for (int i = 0; modules[i].name != NULL; i++) | ||||
|   { | ||||
|     if (strcmp(name, modules[i].name) == 0) return &modules[i]; | ||||
|   } | ||||
| 
 | ||||
|   return NULL; | ||||
| } | ||||
| 
 | ||||
| // Looks for a class with [name] in [module].
 | ||||
| static ClassRegistry* findClass(ModuleRegistry* module, const char* name) | ||||
| { | ||||
|   for (int i = 0; module->classes[i].name != NULL; i++) | ||||
|   { | ||||
|     if (strcmp(name, module->classes[i].name) == 0) return &module->classes[i]; | ||||
|   } | ||||
| 
 | ||||
|   return NULL; | ||||
| } | ||||
| 
 | ||||
| // Looks for a method with [signature] in [clas].
 | ||||
| static WrenForeignMethodFn findMethod(ClassRegistry* clas, | ||||
|                                       bool isStatic, const char* signature) | ||||
| { | ||||
|   for (int i = 0; clas->methods[i].signature != NULL; i++) | ||||
|   { | ||||
|     MethodRegistry* method = &clas->methods[i]; | ||||
|     if (isStatic == method->isStatic && | ||||
|         strcmp(signature, method->signature) == 0) | ||||
|     { | ||||
|       return method->method; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return NULL; | ||||
| } | ||||
| 
 | ||||
| char* readBuiltInModule(const char* name) | ||||
| { | ||||
|   ModuleRegistry* module = findModule(name); | ||||
|   if (module == NULL) return NULL; | ||||
| 
 | ||||
|   size_t length = strlen(*module->source); | ||||
|   char* copy = (char*)malloc(length + 1); | ||||
|   memcpy(copy, *module->source, length + 1); | ||||
|   return copy; | ||||
| } | ||||
| 
 | ||||
| WrenForeignMethodFn bindBuiltInForeignMethod( | ||||
|     WrenVM* vm, const char* moduleName, const char* className, bool isStatic, | ||||
|     const char* signature) | ||||
| { | ||||
|   // TODO: Assert instead of return NULL?
 | ||||
|   ModuleRegistry* module = findModule(moduleName); | ||||
|   if (module == NULL) return NULL; | ||||
| 
 | ||||
|   ClassRegistry* clas = findClass(module, className); | ||||
|   if (clas == NULL) return NULL; | ||||
| 
 | ||||
|   return findMethod(clas, isStatic, signature); | ||||
| } | ||||
| 
 | ||||
| WrenForeignClassMethods bindBuiltInForeignClass( | ||||
|     WrenVM* vm, const char* moduleName, const char* className) | ||||
| { | ||||
|   WrenForeignClassMethods methods = { NULL, NULL }; | ||||
| 
 | ||||
|   ModuleRegistry* module = findModule(moduleName); | ||||
|   if (module == NULL) return methods; | ||||
| 
 | ||||
|   ClassRegistry* clas = findClass(module, className); | ||||
|   if (clas == NULL) return methods; | ||||
| 
 | ||||
|   methods.allocate = findMethod(clas, true, "<allocate>"); | ||||
|   methods.finalize = (WrenFinalizerFn)findMethod(clas, true, "<finalize>"); | ||||
| 
 | ||||
|   return methods; | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/logic/wren/cli/modules.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/logic/wren/cli/modules.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| #ifndef modules_h | ||||
| #define modules_h | ||||
| 
 | ||||
| // This wires up all of the foreign classes and methods defined by the built-in
 | ||||
| // modules bundled with the CLI.
 | ||||
| 
 | ||||
| #include "wren.h" | ||||
| 
 | ||||
| // Returns the source for built-in module [name].
 | ||||
| char* readBuiltInModule(const char* module); | ||||
| 
 | ||||
| // Looks up a foreign method in a built-in module.
 | ||||
| //
 | ||||
| // Returns `NULL` if [moduleName] is not a built-in module.
 | ||||
| WrenForeignMethodFn bindBuiltInForeignMethod( | ||||
|     WrenVM* vm, const char* moduleName, const char* className, bool isStatic, | ||||
|     const char* signature); | ||||
| 
 | ||||
| // Binds foreign classes declared in a built-in modules.
 | ||||
| WrenForeignClassMethods bindBuiltInForeignClass( | ||||
|     WrenVM* vm, const char* moduleName, const char* className); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										312
									
								
								src/logic/wren/cli/path.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								src/logic/wren/cli/path.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,312 @@ | |||
| #include <stdbool.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "path.h" | ||||
| 
 | ||||
| // The maximum number of components in a path. We can't normalize a path that
 | ||||
| // contains more than this number of parts. The number here assumes a max path
 | ||||
| // length of 4096, which is common on Linux, and then assumes each component is
 | ||||
| // at least two characters, "/", and a single-letter directory name.
 | ||||
| #define MAX_COMPONENTS 2048 | ||||
| 
 | ||||
| typedef struct { | ||||
|   const char* start; | ||||
|   const char* end; | ||||
| } Slice; | ||||
| 
 | ||||
| static void ensureCapacity(Path* path, size_t capacity) | ||||
| { | ||||
|   // Capacity always needs to be one greater than the actual length to have
 | ||||
|   // room for the null byte, which is stored in the buffer, but not counted in
 | ||||
|   // the length. A zero-character path still needs a one-character array to
 | ||||
|   // store the '\0'.
 | ||||
|   capacity++; | ||||
|    | ||||
|   if (path->capacity >= capacity) return; | ||||
|    | ||||
|   // Grow by doubling in size.
 | ||||
|   size_t newCapacity = 16; | ||||
|   while (newCapacity < capacity) newCapacity *= 2; | ||||
|    | ||||
|   path->chars = (char*)realloc(path->chars, newCapacity); | ||||
|   path->capacity = newCapacity; | ||||
| } | ||||
| 
 | ||||
| static void appendSlice(Path* path, Slice slice) | ||||
| { | ||||
|   size_t length = slice.end - slice.start; | ||||
|   ensureCapacity(path, path->length + length); | ||||
|   memcpy(path->chars + path->length, slice.start, length); | ||||
|   path->length += length; | ||||
|   path->chars[path->length] = '\0'; | ||||
| } | ||||
| 
 | ||||
| static bool isSeparator(char c) | ||||
| { | ||||
|   // Slash is a separator on POSIX and Windows.
 | ||||
|   if (c == '/') return true; | ||||
|    | ||||
|   // Backslash is only a separator on Windows.
 | ||||
| #ifdef _WIN32 | ||||
|   if (c == '\\') return true; | ||||
| #endif | ||||
|    | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| static bool isDriveLetter(char c) | ||||
| { | ||||
|   return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| // Gets the length of the prefix of [path] that defines its absolute root.
 | ||||
| //
 | ||||
| // Returns 1 the leading "/". On Windows, also handles drive letters ("C:" or
 | ||||
| // "C:\").
 | ||||
| //
 | ||||
| // If the path is not absolute, returns 0.
 | ||||
| static size_t absolutePrefixLength(const char* path) | ||||
| { | ||||
| #ifdef _WIN32 | ||||
|   // Drive letter.
 | ||||
|   if (isDriveLetter(path[0]) && path[1] == ':') | ||||
|   { | ||||
|     if (isSeparator(path[2])) | ||||
|     { | ||||
|       // Fully absolute path.
 | ||||
|       return 3; | ||||
|     } else { | ||||
|       // "Half-absolute" path like "C:", which is relative to the current
 | ||||
|       // working directory on drive. It's absolute for our purposes.
 | ||||
|       return 2; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // TODO: UNC paths.
 | ||||
| 
 | ||||
| #endif | ||||
|    | ||||
|   // POSIX-style absolute path or absolute path in the current drive on Windows.
 | ||||
|   if (isSeparator(path[0])) return 1; | ||||
| 
 | ||||
|   // Not absolute.
 | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| PathType pathType(const char* path) | ||||
| { | ||||
|   if (absolutePrefixLength(path) > 0) return PATH_TYPE_ABSOLUTE; | ||||
| 
 | ||||
|   // See if it must be relative.
 | ||||
|   if ((path[0] == '.' && isSeparator(path[1])) || | ||||
|       (path[0] == '.' && path[1] == '.' && isSeparator(path[2]))) | ||||
|   { | ||||
|     return PATH_TYPE_RELATIVE; | ||||
|   } | ||||
|    | ||||
|   // Otherwise, we don't know.
 | ||||
|   return PATH_TYPE_SIMPLE; | ||||
| } | ||||
| 
 | ||||
| Path* pathNew(const char* string) | ||||
| { | ||||
|   Path* path = (Path*)malloc(sizeof(Path)); | ||||
|   path->chars = (char*)malloc(1); | ||||
|   path->chars[0] = '\0'; | ||||
|   path->length = 0; | ||||
|   path->capacity = 0; | ||||
| 
 | ||||
|   pathAppendString(path, string); | ||||
| 
 | ||||
|   return path; | ||||
| } | ||||
| 
 | ||||
| void pathFree(Path* path) | ||||
| { | ||||
|   if (path->chars) free(path->chars); | ||||
|   free(path); | ||||
| } | ||||
| 
 | ||||
| void pathDirName(Path* path) | ||||
| { | ||||
|   // Find the last path separator.
 | ||||
|   for (size_t i = path->length - 1; i < path->length; i--) | ||||
|   { | ||||
|     if (isSeparator(path->chars[i])) | ||||
|     { | ||||
|       path->length = i; | ||||
|       path->chars[i] = '\0'; | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // If we got here, there was no separator so it must be a single component.
 | ||||
|   path->length = 0; | ||||
|   path->chars[0] = '\0'; | ||||
| } | ||||
| 
 | ||||
| void pathRemoveExtension(Path* path) | ||||
| { | ||||
|   for (size_t i = path->length - 1; i < path->length; i--) | ||||
|   { | ||||
|     // If we hit a path separator before finding the extension, then the last
 | ||||
|     // component doesn't have one.
 | ||||
|     if (isSeparator(path->chars[i])) return; | ||||
|      | ||||
|     if (path->chars[i] == '.') | ||||
|     { | ||||
|       path->length = i; | ||||
|       path->chars[path->length] = '\0'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void pathJoin(Path* path, const char* string) | ||||
| { | ||||
|   if (path->length > 0 && !isSeparator(path->chars[path->length - 1])) | ||||
|   { | ||||
|     pathAppendChar(path, '/'); | ||||
|   } | ||||
| 
 | ||||
|   pathAppendString(path, string); | ||||
| } | ||||
| 
 | ||||
| void pathAppendChar(Path* path, char c) | ||||
| { | ||||
|   ensureCapacity(path, path->length + 1); | ||||
|   path->chars[path->length++] = c; | ||||
|   path->chars[path->length] = '\0'; | ||||
| } | ||||
| 
 | ||||
| void pathAppendString(Path* path, const char* string) | ||||
| { | ||||
|   Slice slice; | ||||
|   slice.start = string; | ||||
|   slice.end = string + strlen(string); | ||||
|   appendSlice(path, slice); | ||||
| } | ||||
| 
 | ||||
| void pathNormalize(Path* path) | ||||
| { | ||||
|   // Split the path into components.
 | ||||
|   Slice components[MAX_COMPONENTS]; | ||||
|   int numComponents = 0; | ||||
|    | ||||
|   char* start = path->chars; | ||||
|   char* end = path->chars; | ||||
| 
 | ||||
|   // Split into parts and handle "." and "..".
 | ||||
|   int leadingDoubles = 0; | ||||
|   for (;;) | ||||
|   { | ||||
|     if (*end == '\0' || isSeparator(*end)) | ||||
|     { | ||||
|       // Add the current component.
 | ||||
|       if (start != end) | ||||
|       { | ||||
|         size_t length = end - start; | ||||
|         if (length == 1 && start[0] == '.') | ||||
|         { | ||||
|           // Skip "." components.
 | ||||
|         } | ||||
|         else if (length == 2 && start[0] == '.' && start[1] == '.') | ||||
|         { | ||||
|           // Walk out of directories on "..".
 | ||||
|           if (numComponents > 0) | ||||
|           { | ||||
|             // Discard the previous component.
 | ||||
|             numComponents--; | ||||
|           } | ||||
|           else | ||||
|           { | ||||
|             // Can't back out any further, so preserve the "..".
 | ||||
|             leadingDoubles++; | ||||
|           } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|           if (numComponents >= MAX_COMPONENTS) | ||||
|           { | ||||
|             fprintf(stderr, "Path cannot have more than %d path components.\n", | ||||
|                     MAX_COMPONENTS); | ||||
|             exit(1); | ||||
|           } | ||||
|            | ||||
|           components[numComponents].start = start; | ||||
|           components[numComponents].end = end; | ||||
|           numComponents++; | ||||
|         } | ||||
|       } | ||||
|        | ||||
|       // Skip over separators.
 | ||||
|       while (*end != '\0' && isSeparator(*end)) end++; | ||||
|        | ||||
|       start = end; | ||||
|       if (*end == '\0') break; | ||||
|     } | ||||
|      | ||||
|     end++; | ||||
|   } | ||||
|    | ||||
|   // Preserve the path type. We don't want to turn, say, "./foo" into "foo"
 | ||||
|   // because that changes the semantics of how that path is handled when used
 | ||||
|   // as an import string.
 | ||||
|   bool needsSeparator = false; | ||||
| 
 | ||||
|   Path* result = pathNew(""); | ||||
|   size_t prefixLength = absolutePrefixLength(path->chars); | ||||
|   if (prefixLength > 0) | ||||
|   { | ||||
|     // It's an absolute path, so preserve the absolute prefix.
 | ||||
|     Slice slice; | ||||
|     slice.start = path->chars; | ||||
|     slice.end = path->chars + prefixLength; | ||||
|     appendSlice(result, slice); | ||||
|   } | ||||
|   else if (leadingDoubles > 0) | ||||
|   { | ||||
|     // Add any leading "..".
 | ||||
|     for (int i = 0; i < leadingDoubles; i++) | ||||
|     { | ||||
|       if (needsSeparator) pathAppendChar(result, '/'); | ||||
|       pathAppendString(result, ".."); | ||||
|       needsSeparator = true; | ||||
|     } | ||||
|   } | ||||
|   else if (path->chars[0] == '.' && isSeparator(path->chars[1])) | ||||
|   { | ||||
|     // Preserve a leading "./", since we use that to distinguish relative from
 | ||||
|     // logical imports.
 | ||||
|     pathAppendChar(result, '.'); | ||||
|     needsSeparator = true; | ||||
|   } | ||||
|    | ||||
|   for (int i = 0; i < numComponents; i++) | ||||
|   { | ||||
|     if (needsSeparator) pathAppendChar(result, '/'); | ||||
|     appendSlice(result, components[i]); | ||||
|     needsSeparator = true; | ||||
|   } | ||||
|    | ||||
|   if (result->length == 0) pathAppendChar(result, '.'); | ||||
|    | ||||
|   // Copy back into the original path.
 | ||||
|   free(path->chars); | ||||
|   path->capacity = result->capacity; | ||||
|   path->chars = result->chars; | ||||
|   path->length = result->length; | ||||
|    | ||||
|   free(result); | ||||
| } | ||||
| 
 | ||||
| char* pathToString(Path* path) | ||||
| { | ||||
|   char* string = (char*)malloc(path->length + 1); | ||||
|   memcpy(string, path->chars, path->length); | ||||
|   string[path->length] = '\0'; | ||||
|   return string; | ||||
| } | ||||
							
								
								
									
										65
									
								
								src/logic/wren/cli/path.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/logic/wren/cli/path.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | |||
| #ifndef path_h | ||||
| #define path_h | ||||
| 
 | ||||
| // Path manipulation functions.
 | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   // Dynamically allocated array of characters.
 | ||||
|   char* chars; | ||||
| 
 | ||||
|   // The number of characters currently in use in [chars], not including the
 | ||||
|   // null terminator.
 | ||||
|   size_t length; | ||||
| 
 | ||||
|   // Size of the allocated [chars] buffer.
 | ||||
|   size_t capacity; | ||||
| } Path; | ||||
| 
 | ||||
| // Categorizes what form a path is.
 | ||||
| typedef enum | ||||
| { | ||||
|   // An absolute path, starting with "/" on POSIX systems, a drive letter on
 | ||||
|   // Windows, etc.
 | ||||
|   PATH_TYPE_ABSOLUTE, | ||||
|    | ||||
|   // An explicitly relative path, starting with "./" or "../".
 | ||||
|   PATH_TYPE_RELATIVE, | ||||
|    | ||||
|   // A path that has no leading prefix, like "foo/bar".
 | ||||
|   PATH_TYPE_SIMPLE, | ||||
| } PathType; | ||||
| 
 | ||||
| PathType pathType(const char* path); | ||||
| 
 | ||||
| // Creates a new empty path.
 | ||||
| Path* pathNew(const char* string); | ||||
| 
 | ||||
| // Releases the method associated with [path].
 | ||||
| void pathFree(Path* path); | ||||
| 
 | ||||
| // Strips off the last component of the path name.
 | ||||
| void pathDirName(Path* path); | ||||
| 
 | ||||
| // Strips off the file extension from the last component of the path.
 | ||||
| void pathRemoveExtension(Path* path); | ||||
| 
 | ||||
| // Appends [string] to [path].
 | ||||
| void pathJoin(Path* path, const char* string); | ||||
| 
 | ||||
| // Appends [c] to the path, growing the buffer if needed.
 | ||||
| void pathAppendChar(Path* path, char c); | ||||
| 
 | ||||
| // Appends [string] to the path, growing the buffer if needed.
 | ||||
| void pathAppendString(Path* path, const char* string); | ||||
| 
 | ||||
| // Simplifies the path string as much as possible.
 | ||||
| //
 | ||||
| // Applies and removes any "." or ".." components, collapses redundant "/"
 | ||||
| // characters, and normalizes all path separators to "/".
 | ||||
| void pathNormalize(Path* path); | ||||
| 
 | ||||
| // Allocates a new string exactly the right length and copies this path to it.
 | ||||
| char* pathToString(Path* path); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										32
									
								
								src/logic/wren/cli/stat.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/logic/wren/cli/stat.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| #ifndef stat_h | ||||
| #define stat_h | ||||
| 
 | ||||
| // Utilities to smooth over working with stat() in a cross-platform way.
 | ||||
| 
 | ||||
| // Windows doesn't define all of the Unix permission and mode flags by default,
 | ||||
| // so map them ourselves.
 | ||||
| #if defined(WIN32) || defined(WIN64) | ||||
|   #include <sys\stat.h> | ||||
| 
 | ||||
|   // Map to Windows permission flags.
 | ||||
|   #ifndef S_IRUSR | ||||
|   #define S_IRUSR _S_IREAD | ||||
|   #endif | ||||
| 
 | ||||
|   #ifndef S_IWUSR | ||||
|   #define S_IWUSR _S_IWRITE | ||||
|   #endif | ||||
| 
 | ||||
|   #ifndef S_ISREG | ||||
|   #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) | ||||
|   #endif | ||||
| 
 | ||||
|   #ifndef S_ISDIR | ||||
|   #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) | ||||
|   #endif | ||||
| 
 | ||||
|   // Not supported on Windows.
 | ||||
|   #define O_SYNC 0 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										405
									
								
								src/logic/wren/cli/vm.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										405
									
								
								src/logic/wren/cli/vm.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,405 @@ | |||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "io.h" | ||||
| #include "modules.h" | ||||
| #include "path.h" | ||||
| #include "scheduler.h" | ||||
| #include "stat.h" | ||||
| #include "vm.h" | ||||
| 
 | ||||
| // The single VM instance that the CLI uses.
 | ||||
| static WrenVM* vm; | ||||
| 
 | ||||
| static WrenBindForeignMethodFn bindMethodFn = NULL; | ||||
| static WrenBindForeignClassFn bindClassFn = NULL; | ||||
| static WrenForeignMethodFn afterLoadFn = NULL; | ||||
| 
 | ||||
| static uv_loop_t* loop; | ||||
| 
 | ||||
| // TODO: This isn't currently used, but probably will be when package imports
 | ||||
| // are supported. If not then, then delete this.
 | ||||
| static char* rootDirectory = NULL; | ||||
| static Path* wrenModulesDirectory = NULL; | ||||
| 
 | ||||
| // The exit code to use unless some other error overrides it.
 | ||||
| int defaultExitCode = 0; | ||||
| 
 | ||||
| // Reads the contents of the file at [path] and returns it as a heap allocated
 | ||||
| // string.
 | ||||
| //
 | ||||
| // Returns `NULL` if the path could not be found. Exits if it was found but
 | ||||
| // could not be read.
 | ||||
| static char* readFile(const char* path) | ||||
| { | ||||
|   FILE* file = fopen(path, "rb"); | ||||
|   if (file == NULL) return NULL; | ||||
|    | ||||
|   // Find out how big the file is.
 | ||||
|   fseek(file, 0L, SEEK_END); | ||||
|   size_t fileSize = ftell(file); | ||||
|   rewind(file); | ||||
|    | ||||
|   // Allocate a buffer for it.
 | ||||
|   char* buffer = (char*)malloc(fileSize + 1); | ||||
|   if (buffer == NULL) | ||||
|   { | ||||
|     fprintf(stderr, "Could not read file \"%s\".\n", path); | ||||
|     exit(74); | ||||
|   } | ||||
|    | ||||
|   // Read the entire file.
 | ||||
|   size_t bytesRead = fread(buffer, 1, fileSize, file); | ||||
|   if (bytesRead < fileSize) | ||||
|   { | ||||
|     fprintf(stderr, "Could not read file \"%s\".\n", path); | ||||
|     exit(74); | ||||
|   } | ||||
|    | ||||
|   // Terminate the string.
 | ||||
|   buffer[bytesRead] = '\0'; | ||||
|    | ||||
|   fclose(file); | ||||
|   return buffer; | ||||
| } | ||||
| 
 | ||||
| static bool isDirectory(Path* path) | ||||
| { | ||||
|   uv_fs_t request; | ||||
|   uv_fs_stat(loop, &request, path->chars, NULL); | ||||
|   // TODO: Check request.result value?
 | ||||
|    | ||||
|   bool result = request.result == 0 && S_ISDIR(request.statbuf.st_mode); | ||||
|    | ||||
|   uv_fs_req_cleanup(&request); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| static Path* realPath(Path* path) | ||||
| { | ||||
|   uv_fs_t request; | ||||
|   uv_fs_realpath(loop, &request, path->chars, NULL); | ||||
|    | ||||
|   Path* result = pathNew((char*)request.ptr); | ||||
|    | ||||
|   uv_fs_req_cleanup(&request); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| // Starting at [rootDirectory], walks up containing directories looking for a
 | ||||
| // nearby "wren_modules" directory. If found, stores it in
 | ||||
| // [wrenModulesDirectory].
 | ||||
| //
 | ||||
| // If [wrenModulesDirectory] has already been found, does nothing.
 | ||||
| static void findModulesDirectory() | ||||
| { | ||||
|   if (wrenModulesDirectory != NULL) return; | ||||
|    | ||||
|   Path* searchDirectory = pathNew(rootDirectory); | ||||
|   Path* lastPath = realPath(searchDirectory); | ||||
| 
 | ||||
|   // Keep walking up directories as long as we find them.
 | ||||
|   for (;;) | ||||
|   { | ||||
|     Path* modulesDirectory = pathNew(searchDirectory->chars); | ||||
|     pathJoin(modulesDirectory, "wren_modules"); | ||||
|      | ||||
|     if (isDirectory(modulesDirectory)) | ||||
|     { | ||||
|       pathNormalize(modulesDirectory); | ||||
|       wrenModulesDirectory = modulesDirectory; | ||||
|       pathFree(lastPath); | ||||
|       break; | ||||
|     } | ||||
|      | ||||
|     pathFree(modulesDirectory); | ||||
|      | ||||
|     // Walk up directories until we hit the root. We can tell that because
 | ||||
|     // adding ".." yields the same real path.
 | ||||
|     pathJoin(searchDirectory, ".."); | ||||
|     Path* thisPath = realPath(searchDirectory); | ||||
|     if (strcmp(lastPath->chars, thisPath->chars) == 0) | ||||
|     { | ||||
|       pathFree(thisPath); | ||||
|       break; | ||||
|     } | ||||
|      | ||||
|     pathFree(lastPath); | ||||
|     lastPath = thisPath; | ||||
|   } | ||||
|    | ||||
|   pathFree(searchDirectory); | ||||
| } | ||||
| 
 | ||||
| // Applies the CLI's import resolution policy. The rules are:
 | ||||
| //
 | ||||
| // * If [module] starts with "./" or "../", it is a relative import, relative
 | ||||
| //   to [importer]. The resolved path is [name] concatenated onto the directory
 | ||||
| //   containing [importer] and then normalized.
 | ||||
| //
 | ||||
| //   For example, importing "./a/./b/../c" from "./d/e/f" gives you "./d/e/a/c".
 | ||||
| static const char* resolveModule(WrenVM* vm, const char* importer, | ||||
|                                  const char* module) | ||||
| { | ||||
|   // Logical import strings are used as-is and need no resolution.
 | ||||
|   if (pathType(module) == PATH_TYPE_SIMPLE) return module; | ||||
|    | ||||
|   // Get the directory containing the importing module.
 | ||||
|   Path* path = pathNew(importer); | ||||
|   pathDirName(path); | ||||
|    | ||||
|   // Add the relative import path.
 | ||||
|   pathJoin(path, module); | ||||
|    | ||||
|   pathNormalize(path); | ||||
|   char* resolved = pathToString(path); | ||||
|    | ||||
|   pathFree(path); | ||||
|   return resolved; | ||||
| } | ||||
| 
 | ||||
| // Attempts to read the source for [module] relative to the current root
 | ||||
| // directory.
 | ||||
| //
 | ||||
| // Returns it if found, or NULL if the module could not be found. Exits if the
 | ||||
| // module was found but could not be read.
 | ||||
| static char* readModule(WrenVM* vm, const char* module) | ||||
| { | ||||
|   Path* filePath; | ||||
|   if (pathType(module) == PATH_TYPE_SIMPLE) | ||||
|   { | ||||
|     // If there is no "wren_modules" directory, then the only logical imports
 | ||||
|     // we can handle are built-in ones. Let the VM try to handle it.
 | ||||
|     findModulesDirectory(); | ||||
|     if (wrenModulesDirectory == NULL) return readBuiltInModule(module); | ||||
|      | ||||
|     // TODO: Should we explicitly check for the existence of the module's base
 | ||||
|     // directory inside "wren_modules" here?
 | ||||
|      | ||||
|     // Look up the module in "wren_modules".
 | ||||
|     filePath = pathNew(wrenModulesDirectory->chars); | ||||
|     pathJoin(filePath, module); | ||||
|      | ||||
|     // If the module is a single bare name, treat it as a module with the same
 | ||||
|     // name inside the package. So "foo" means "foo/foo".
 | ||||
|     if (strchr(module, '/') == NULL) pathJoin(filePath, module); | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     // The module path is already a file path.
 | ||||
|     filePath = pathNew(module); | ||||
|   } | ||||
|    | ||||
|   // Add a ".wren" file extension.
 | ||||
|   pathAppendString(filePath, ".wren"); | ||||
| 
 | ||||
|   char* source = readFile(filePath->chars); | ||||
|   pathFree(filePath); | ||||
|    | ||||
|   // If we didn't find it, it may be a module built into the CLI or VM, so keep
 | ||||
|   // going.
 | ||||
|   if (source != NULL) return source; | ||||
| 
 | ||||
|   // Otherwise, see if it's a built-in module.
 | ||||
|   return readBuiltInModule(module); | ||||
| } | ||||
| 
 | ||||
| // 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) | ||||
| { | ||||
|   WrenForeignMethodFn method = bindBuiltInForeignMethod(vm, module, className, | ||||
|                                                         isStatic, signature); | ||||
|   if (method != NULL) return method; | ||||
|    | ||||
|   if (bindMethodFn != NULL) | ||||
|   { | ||||
|     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 = bindBuiltInForeignClass(vm, module, | ||||
|                                                             className); | ||||
|   if (methods.allocate != NULL) return methods; | ||||
| 
 | ||||
|   if (bindClassFn != NULL) | ||||
|   { | ||||
|     return bindClassFn(vm, module, className); | ||||
|   } | ||||
| 
 | ||||
|   return methods; | ||||
| } | ||||
| 
 | ||||
| static void write(WrenVM* vm, const char* text) | ||||
| { | ||||
|   printf("%s", text); | ||||
| } | ||||
| 
 | ||||
| static void reportError(WrenVM* vm, WrenErrorType type, | ||||
|                         const char* module, int line, const char* message) | ||||
| { | ||||
|   switch (type) | ||||
|   { | ||||
|     case WREN_ERROR_COMPILE: | ||||
|       fprintf(stderr, "[%s line %d] %s\n", module, line, message); | ||||
|       break; | ||||
|        | ||||
|     case WREN_ERROR_RUNTIME: | ||||
|       fprintf(stderr, "%s\n", message); | ||||
|       break; | ||||
|        | ||||
|     case WREN_ERROR_STACK_TRACE: | ||||
|       fprintf(stderr, "[%s line %d] in %s\n", module, line, message); | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void initVM() | ||||
| { | ||||
|   WrenConfiguration config; | ||||
|   wrenInitConfiguration(&config); | ||||
| 
 | ||||
|   config.bindForeignMethodFn = bindForeignMethod; | ||||
|   config.bindForeignClassFn = bindForeignClass; | ||||
|   config.resolveModuleFn = resolveModule; | ||||
|   config.loadModuleFn = readModule; | ||||
|   config.writeFn = write; | ||||
|   config.errorFn = reportError; | ||||
| 
 | ||||
|   // Since we're running in a standalone process, be generous with memory.
 | ||||
|   config.initialHeapSize = 1024 * 1024 * 100; | ||||
|   vm = wrenNewVM(&config); | ||||
| 
 | ||||
|   // Initialize the event loop.
 | ||||
|   loop = (uv_loop_t*)malloc(sizeof(uv_loop_t)); | ||||
|   uv_loop_init(loop); | ||||
| } | ||||
| 
 | ||||
| static void freeVM() | ||||
| { | ||||
|   ioShutdown(); | ||||
|   schedulerShutdown(); | ||||
|    | ||||
|   uv_loop_close(loop); | ||||
|   free(loop); | ||||
|    | ||||
|   wrenFreeVM(vm); | ||||
| 
 | ||||
|   uv_tty_reset_mode(); | ||||
|    | ||||
|   if (wrenModulesDirectory != NULL) pathFree(wrenModulesDirectory); | ||||
| } | ||||
| 
 | ||||
| WrenInterpretResult runFile(const char* path) | ||||
| { | ||||
|   char* source = readFile(path); | ||||
|   if (source == NULL) | ||||
|   { | ||||
|     fprintf(stderr, "Could not find file \"%s\".\n", path); | ||||
|     exit(66); | ||||
|   } | ||||
| 
 | ||||
|   // If it looks like a relative path, make it explicitly relative so that we
 | ||||
|   // can distinguish it from logical paths.
 | ||||
|   // TODO: It might be nice to be able to run scripts from within a surrounding
 | ||||
|   // "wren_modules" directory by passing in a simple path like "foo/bar". In
 | ||||
|   // that case, here, we could check to see whether the give path exists inside
 | ||||
|   // "wren_modules" or as a relative path and choose to add "./" or not based
 | ||||
|   // on that.
 | ||||
|   Path* module = pathNew(path); | ||||
|   if (pathType(module->chars) == PATH_TYPE_SIMPLE) | ||||
|   { | ||||
|     Path* relative = pathNew("."); | ||||
|     pathJoin(relative, path); | ||||
| 
 | ||||
|     pathFree(module); | ||||
|     module = relative; | ||||
|   } | ||||
| 
 | ||||
|   pathRemoveExtension(module); | ||||
| 
 | ||||
|   // Use the directory where the file is as the root to resolve imports
 | ||||
|   // relative to.
 | ||||
|   Path* directory = pathNew(module->chars); | ||||
|    | ||||
|   pathDirName(directory); | ||||
|   rootDirectory = pathToString(directory); | ||||
|   pathFree(directory); | ||||
|    | ||||
|   initVM(); | ||||
| 
 | ||||
|   WrenInterpretResult result = wrenInterpret(vm, module->chars, source); | ||||
| 
 | ||||
|   if (afterLoadFn != NULL) afterLoadFn(vm); | ||||
|    | ||||
|   if (result == WREN_RESULT_SUCCESS) | ||||
|   { | ||||
|     uv_run(loop, UV_RUN_DEFAULT); | ||||
|   } | ||||
| 
 | ||||
|   freeVM(); | ||||
| 
 | ||||
|   free(source); | ||||
|   free(rootDirectory); | ||||
|   pathFree(module); | ||||
| 
 | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| WrenInterpretResult runRepl() | ||||
| { | ||||
|   // This cast is safe since we don't try to free the string later.
 | ||||
|   rootDirectory = (char*)"."; | ||||
|   initVM(); | ||||
| 
 | ||||
|   printf("\\\\/\"-\n"); | ||||
|   printf(" \\_/   wren v%s\n", WREN_VERSION_STRING); | ||||
| 
 | ||||
|   WrenInterpretResult result = wrenInterpret(vm, "<repl>", "import \"repl\"\n"); | ||||
|    | ||||
|   if (result == WREN_RESULT_SUCCESS) | ||||
|   { | ||||
|     uv_run(loop, UV_RUN_DEFAULT); | ||||
|   } | ||||
| 
 | ||||
|   freeVM(); | ||||
|    | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| WrenVM* getVM() | ||||
| { | ||||
|   return vm; | ||||
| } | ||||
| 
 | ||||
| uv_loop_t* getLoop() | ||||
| { | ||||
|   return loop; | ||||
| } | ||||
| 
 | ||||
| int getExitCode() | ||||
| { | ||||
|   return defaultExitCode; | ||||
| } | ||||
| 
 | ||||
| void setExitCode(int exitCode) | ||||
| { | ||||
|   defaultExitCode = exitCode; | ||||
| } | ||||
| 
 | ||||
| void setTestCallbacks(WrenBindForeignMethodFn bindMethod, | ||||
|                       WrenBindForeignClassFn bindClass, | ||||
|                       WrenForeignMethodFn afterLoad) | ||||
| { | ||||
|   bindMethodFn = bindMethod; | ||||
|   bindClassFn = bindClass; | ||||
|   afterLoadFn = afterLoad; | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/logic/wren/cli/vm.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/logic/wren/cli/vm.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| #ifndef vm_h | ||||
| #define vm_h | ||||
| 
 | ||||
| #include "uv.h" | ||||
| #include "wren.h" | ||||
| 
 | ||||
| // Executes the Wren script at [path] in a new VM.
 | ||||
| WrenInterpretResult runFile(const char* path); | ||||
| 
 | ||||
| // Runs the Wren interactive REPL.
 | ||||
| WrenInterpretResult runRepl(); | ||||
| 
 | ||||
| // Gets the currently running VM.
 | ||||
| WrenVM* getVM(); | ||||
| 
 | ||||
| // Gets the event loop the VM is using.
 | ||||
| uv_loop_t* getLoop(); | ||||
| 
 | ||||
| // Get the exit code the CLI should exit with when done.
 | ||||
| int getExitCode(); | ||||
| 
 | ||||
| // Set the exit code the CLI should exit with when done.
 | ||||
| void setExitCode(int exitCode); | ||||
| 
 | ||||
| // 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 setTestCallbacks(WrenBindForeignMethodFn bindMethod, | ||||
|                       WrenBindForeignClassFn bindClass, | ||||
|                       void (*afterLoad)(WrenVM* vm)); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										488
									
								
								src/logic/wren/include/wren.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										488
									
								
								src/logic/wren/include/wren.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,488 @@ | |||
| #ifndef wren_h | ||||
| #define wren_h | ||||
| 
 | ||||
| #include <stdarg.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| // The Wren semantic version number components.
 | ||||
| #define WREN_VERSION_MAJOR 0 | ||||
| #define WREN_VERSION_MINOR 1 | ||||
| #define WREN_VERSION_PATCH 0 | ||||
| 
 | ||||
| // A human-friendly string representation of the version.
 | ||||
| #define WREN_VERSION_STRING "0.1.0" | ||||
| 
 | ||||
| // A monotonically increasing numeric representation of the version number. Use
 | ||||
| // this if you want to do range checks over versions.
 | ||||
| #define WREN_VERSION_NUMBER (WREN_VERSION_MAJOR * 1000000 + \ | ||||
|                              WREN_VERSION_MINOR * 1000 + \ | ||||
|                              WREN_VERSION_PATCH) | ||||
| 
 | ||||
| // A single virtual machine for executing Wren code.
 | ||||
| //
 | ||||
| // Wren has no global state, so all state stored by a running interpreter lives
 | ||||
| // here.
 | ||||
| typedef struct WrenVM WrenVM; | ||||
| 
 | ||||
| // A handle to a Wren object.
 | ||||
| //
 | ||||
| // This lets code outside of the VM hold a persistent reference to an object.
 | ||||
| // After a handle is acquired, and until it is released, this ensures the
 | ||||
| // garbage collector will not reclaim the object it references.
 | ||||
| typedef struct WrenHandle WrenHandle; | ||||
| 
 | ||||
| // A generic allocation function that handles all explicit memory management
 | ||||
| // used by Wren. It's used like so:
 | ||||
| //
 | ||||
| // - To allocate new memory, [memory] is NULL and [newSize] is the desired
 | ||||
| //   size. It should return the allocated memory or NULL on failure.
 | ||||
| //
 | ||||
| // - To attempt to grow an existing allocation, [memory] is the memory, and
 | ||||
| //   [newSize] is the desired size. It should return [memory] if it was able to
 | ||||
| //   grow it in place, or a new pointer if it had to move it.
 | ||||
| //
 | ||||
| // - To shrink memory, [memory] and [newSize] are the same as above but it will
 | ||||
| //   always return [memory].
 | ||||
| //
 | ||||
| // - To free memory, [memory] will be the memory to free and [newSize] will be
 | ||||
| //   zero. It should return NULL.
 | ||||
| typedef void* (*WrenReallocateFn)(void* memory, size_t newSize); | ||||
| 
 | ||||
| // A function callable from Wren code, but implemented in C.
 | ||||
| typedef void (*WrenForeignMethodFn)(WrenVM* vm); | ||||
| 
 | ||||
| // A finalizer function for freeing resources owned by an instance of a foreign
 | ||||
| // class. Unlike most foreign methods, finalizers do not have access to the VM
 | ||||
| // and should not interact with it since it's in the middle of a garbage
 | ||||
| // collection.
 | ||||
| typedef void (*WrenFinalizerFn)(void* data); | ||||
| 
 | ||||
| // Gives the host a chance to canonicalize the imported module name,
 | ||||
| // potentially taking into account the (previously resolved) name of the module
 | ||||
| // that contains the import. Typically, this is used to implement relative
 | ||||
| // imports.
 | ||||
| typedef const char* (*WrenResolveModuleFn)(WrenVM* vm, | ||||
|     const char* importer, const char* name); | ||||
| 
 | ||||
| // Loads and returns the source code for the module [name].
 | ||||
| typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name); | ||||
| 
 | ||||
| // Returns a pointer to a foreign method on [className] in [module] with
 | ||||
| // [signature].
 | ||||
| typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm, | ||||
|     const char* module, const char* className, bool isStatic, | ||||
|     const char* signature); | ||||
| 
 | ||||
| // Displays a string of text to the user.
 | ||||
| typedef void (*WrenWriteFn)(WrenVM* vm, const char* text); | ||||
| 
 | ||||
| typedef enum | ||||
| { | ||||
|   // A syntax or resolution error detected at compile time.
 | ||||
|   WREN_ERROR_COMPILE, | ||||
| 
 | ||||
|   // The error message for a runtime error.
 | ||||
|   WREN_ERROR_RUNTIME, | ||||
| 
 | ||||
|   // One entry of a runtime error's stack trace.
 | ||||
|   WREN_ERROR_STACK_TRACE | ||||
| } WrenErrorType; | ||||
| 
 | ||||
| // Reports an error to the user.
 | ||||
| //
 | ||||
| // An error detected during compile time is reported by calling this once with
 | ||||
| // [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line]
 | ||||
| // where the error occurs, and the compiler's error [message].
 | ||||
| //
 | ||||
| // A runtime error is reported by calling this once with [type]
 | ||||
| // `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's
 | ||||
| // [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are
 | ||||
| // made for each line in the stack trace. Each of those has the resolved
 | ||||
| // [module] and [line] where the method or function is defined and [message] is
 | ||||
| // the name of the method or function.
 | ||||
| typedef void (*WrenErrorFn)( | ||||
|     WrenVM* vm, WrenErrorType type, const char* module, int line, | ||||
|     const char* message); | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   // The callback invoked when the foreign object is created.
 | ||||
|   //
 | ||||
|   // This must be provided. Inside the body of this, it must call
 | ||||
|   // [wrenSetSlotNewForeign()] exactly once.
 | ||||
|   WrenForeignMethodFn allocate; | ||||
| 
 | ||||
|   // The callback invoked when the garbage collector is about to collect a
 | ||||
|   // foreign object's memory.
 | ||||
|   //
 | ||||
|   // This may be `NULL` if the foreign class does not need to finalize.
 | ||||
|   WrenFinalizerFn finalize; | ||||
| } WrenForeignClassMethods; | ||||
| 
 | ||||
| // Returns a pair of pointers to the foreign methods used to allocate and
 | ||||
| // finalize the data for instances of [className] in resolved [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.
 | ||||
|   //
 | ||||
|   // If `NULL`, defaults to a built-in function that uses `realloc` and `free`.
 | ||||
|   WrenReallocateFn reallocateFn; | ||||
| 
 | ||||
|   // The callback Wren uses to resolve a module name.
 | ||||
|   //
 | ||||
|   // Some host applications may wish to support "relative" imports, where the
 | ||||
|   // meaning of an import string depends on the module that contains it. To
 | ||||
|   // support that without baking any policy into Wren itself, the VM gives the
 | ||||
|   // host a chance to resolve an import string.
 | ||||
|   //
 | ||||
|   // Before an import is loaded, it calls this, passing in the name of the
 | ||||
|   // module that contains the import and the import string. The host app can
 | ||||
|   // look at both of those and produce a new "canonical" string that uniquely
 | ||||
|   // identifies the module. This string is then used as the name of the module
 | ||||
|   // going forward. It is what is passed to [loadModuleFn], how duplicate
 | ||||
|   // imports of the same module are detected, and how the module is reported in
 | ||||
|   // stack traces.
 | ||||
|   //
 | ||||
|   // If you leave this function NULL, then the original import string is
 | ||||
|   // treated as the resolved string.
 | ||||
|   //
 | ||||
|   // If an import cannot be resolved by the embedder, it should return NULL and
 | ||||
|   // Wren will report that as a runtime error.
 | ||||
|   //
 | ||||
|   // Wren will take ownership of the string you return and free it for you, so
 | ||||
|   // it should be allocated using the same allocation function you provide
 | ||||
|   // above.
 | ||||
|   WrenResolveModuleFn resolveModuleFn; | ||||
| 
 | ||||
|   // The callback Wren uses to load a module.
 | ||||
|   //
 | ||||
|   // Since Wren does not talk directly to the file system, it relies on the
 | ||||
|   // embedder to physically locate and read the source code for a module. The
 | ||||
|   // first time an import appears, Wren will call this and pass in the name of
 | ||||
|   // the module being imported. The VM should return the soure code for that
 | ||||
|   // module. Memory for the source should be allocated using [reallocateFn] and
 | ||||
|   // Wren will take ownership over it.
 | ||||
|   //
 | ||||
|   // This will only be called once for any given module name. Wren caches the
 | ||||
|   // result internally so subsequent imports of the same module will use the
 | ||||
|   // previous source and not call this.
 | ||||
|   //
 | ||||
|   // If a module with the given name could not be found by the embedder, it
 | ||||
|   // should return NULL and Wren will report that as a runtime error.
 | ||||
|   WrenLoadModuleFn loadModuleFn; | ||||
| 
 | ||||
|   // The callback Wren uses to find a foreign method and bind it to a class.
 | ||||
|   //
 | ||||
|   // When a foreign method is declared in a class, this will be called with the
 | ||||
|   // foreign method's module, class, and signature when the class body is
 | ||||
|   // executed. It should return a pointer to the foreign function that will be
 | ||||
|   // bound to that method.
 | ||||
|   //
 | ||||
|   // If the foreign function could not be found, this should return NULL and
 | ||||
|   // 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 callback Wren uses to display text when `System.print()` or the other
 | ||||
|   // related functions are called.
 | ||||
|   //
 | ||||
|   // If this is `NULL`, Wren discards any printed text.
 | ||||
|   WrenWriteFn writeFn; | ||||
| 
 | ||||
|   // The callback Wren uses to report errors.
 | ||||
|   //
 | ||||
|   // When an error occurs, this will be called with the module name, line
 | ||||
|   // number, and an error message. If this is `NULL`, Wren doesn't report any
 | ||||
|   // errors.
 | ||||
|   WrenErrorFn errorFn; | ||||
| 
 | ||||
|   // The number of bytes Wren will allocate before triggering the first garbage
 | ||||
|   // collection.
 | ||||
|   //
 | ||||
|   // If zero, defaults to 10MB.
 | ||||
|   size_t initialHeapSize; | ||||
| 
 | ||||
|   // After a collection occurs, the threshold for the next collection is
 | ||||
|   // determined based on the number of bytes remaining in use. This allows Wren
 | ||||
|   // to shrink its memory usage automatically after reclaiming a large amount
 | ||||
|   // of memory.
 | ||||
|   //
 | ||||
|   // This can be used to ensure that the heap does not get too small, which can
 | ||||
|   // in turn lead to a large number of collections afterwards as the heap grows
 | ||||
|   // back to a usable size.
 | ||||
|   //
 | ||||
|   // If zero, defaults to 1MB.
 | ||||
|   size_t minHeapSize; | ||||
| 
 | ||||
|   // Wren will resize the heap automatically as the number of bytes
 | ||||
|   // remaining in use after a collection changes. This number determines the
 | ||||
|   // amount of additional memory Wren will use after a collection, as a
 | ||||
|   // percentage of the current heap size.
 | ||||
|   //
 | ||||
|   // For example, say that this is 50. After a garbage collection, when there
 | ||||
|   // are 400 bytes of memory still in use, the next collection will be triggered
 | ||||
|   // after a total of 600 bytes are allocated (including the 400 already in
 | ||||
|   // use.)
 | ||||
|   //
 | ||||
|   // Setting this to a smaller number wastes less memory, but triggers more
 | ||||
|   // frequent garbage collections.
 | ||||
|   //
 | ||||
|   // If zero, defaults to 50.
 | ||||
|   int heapGrowthPercent; | ||||
| 
 | ||||
|   // User-defined data associated with the VM.
 | ||||
|   void* userData; | ||||
| 
 | ||||
| } WrenConfiguration; | ||||
| 
 | ||||
| typedef enum | ||||
| { | ||||
|   WREN_RESULT_SUCCESS, | ||||
|   WREN_RESULT_COMPILE_ERROR, | ||||
|   WREN_RESULT_RUNTIME_ERROR | ||||
| } WrenInterpretResult; | ||||
| 
 | ||||
| // The type of an object stored in a slot.
 | ||||
| //
 | ||||
| // This is not necessarily the object's *class*, but instead its low level
 | ||||
| // representation type.
 | ||||
| typedef enum | ||||
| { | ||||
|   WREN_TYPE_BOOL, | ||||
|   WREN_TYPE_NUM, | ||||
|   WREN_TYPE_FOREIGN, | ||||
|   WREN_TYPE_LIST, | ||||
|   WREN_TYPE_NULL, | ||||
|   WREN_TYPE_STRING, | ||||
| 
 | ||||
|   // The object is of a type that isn't accessible by the C API.
 | ||||
|   WREN_TYPE_UNKNOWN | ||||
| } WrenType; | ||||
| 
 | ||||
| // 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
 | ||||
| // configuration.
 | ||||
| WrenVM* wrenNewVM(WrenConfiguration* configuration); | ||||
| 
 | ||||
| // Disposes of all resources is use by [vm], which was previously created by a
 | ||||
| // call to [wrenNewVM].
 | ||||
| void wrenFreeVM(WrenVM* vm); | ||||
| 
 | ||||
| // Immediately run the garbage collector to free unused memory.
 | ||||
| void wrenCollectGarbage(WrenVM* vm); | ||||
| 
 | ||||
| // Runs [source], a string of Wren source code in a new fiber in [vm] in the
 | ||||
| // context of resolved [module].
 | ||||
| WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module, | ||||
|                                   const char* source); | ||||
| 
 | ||||
| // Creates a handle that can be used to invoke a method with [signature] on
 | ||||
| // using a receiver and arguments that are set up on the stack.
 | ||||
| //
 | ||||
| // This handle can be used repeatedly to directly invoke that method from C
 | ||||
| // code using [wrenCall].
 | ||||
| //
 | ||||
| // When you are done with this handle, it must be released using
 | ||||
| // [wrenReleaseHandle].
 | ||||
| WrenHandle* wrenMakeCallHandle(WrenVM* vm, const char* signature); | ||||
| 
 | ||||
| // Calls [method], using the receiver and arguments previously set up on the
 | ||||
| // stack.
 | ||||
| //
 | ||||
| // [method] must have been created by a call to [wrenMakeCallHandle]. The
 | ||||
| // arguments to the method must be already on the stack. The receiver should be
 | ||||
| // in slot 0 with the remaining arguments following it, in order. It is an
 | ||||
| // error if the number of arguments provided does not match the method's
 | ||||
| // signature.
 | ||||
| //
 | ||||
| // After this returns, you can access the return value from slot 0 on the stack.
 | ||||
| WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method); | ||||
| 
 | ||||
| // Releases the reference stored in [handle]. After calling this, [handle] can
 | ||||
| // no longer be used.
 | ||||
| void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle); | ||||
| 
 | ||||
| // The following functions are intended to be called from foreign methods or
 | ||||
| // finalizers. The interface Wren provides to a foreign method is like a
 | ||||
| // register machine: you are given a numbered array of slots that values can be
 | ||||
| // read from and written to. Values always live in a slot (unless explicitly
 | ||||
| // captured using wrenGetSlotHandle(), which ensures the garbage collector can
 | ||||
| // find them.
 | ||||
| //
 | ||||
| // When your foreign function is called, you are given one slot for the receiver
 | ||||
| // and each argument to the method. The receiver is in slot 0 and the arguments
 | ||||
| // are in increasingly numbered slots after that. You are free to read and
 | ||||
| // write to those slots as you want. If you want more slots to use as scratch
 | ||||
| // space, you can call wrenEnsureSlots() to add more.
 | ||||
| //
 | ||||
| // When your function returns, every slot except slot zero is discarded and the
 | ||||
| // value in slot zero is used as the return value of the method. If you don't
 | ||||
| // store a return value in that slot yourself, it will retain its previous
 | ||||
| // value, the receiver.
 | ||||
| //
 | ||||
| // While Wren is dynamically typed, C is not. This means the C interface has to
 | ||||
| // support the various types of primitive values a Wren variable can hold: bool,
 | ||||
| // double, string, etc. If we supported this for every operation in the C API,
 | ||||
| // there would be a combinatorial explosion of functions, like "get a
 | ||||
| // double-valued element from a list", "insert a string key and double value
 | ||||
| // into a map", etc.
 | ||||
| //
 | ||||
| // To avoid that, the only way to convert to and from a raw C value is by going
 | ||||
| // into and out of a slot. All other functions work with values already in a
 | ||||
| // slot. So, to add an element to a list, you put the list in one slot, and the
 | ||||
| // element in another. Then there is a single API function wrenInsertInList()
 | ||||
| // that takes the element out of that slot and puts it into the list.
 | ||||
| //
 | ||||
| // The goal of this API is to be easy to use while not compromising performance.
 | ||||
| // The latter means it does not do type or bounds checking at runtime except
 | ||||
| // using assertions which are generally removed from release builds. C is an
 | ||||
| // unsafe language, so it's up to you to be careful to use it correctly. In
 | ||||
| // return, you get a very fast FFI.
 | ||||
| 
 | ||||
| // Returns the number of slots available to the current foreign method.
 | ||||
| int wrenGetSlotCount(WrenVM* vm); | ||||
| 
 | ||||
| // Ensures that the foreign method stack has at least [numSlots] available for
 | ||||
| // use, growing the stack if needed.
 | ||||
| //
 | ||||
| // Does not shrink the stack if it has more than enough slots.
 | ||||
| //
 | ||||
| // It is an error to call this from a finalizer.
 | ||||
| void wrenEnsureSlots(WrenVM* vm, int numSlots); | ||||
| 
 | ||||
| // Gets the type of the object in [slot].
 | ||||
| WrenType wrenGetSlotType(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Reads a boolean value from [slot].
 | ||||
| //
 | ||||
| // It is an error to call this if the slot does not contain a boolean value.
 | ||||
| bool wrenGetSlotBool(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Reads a byte array from [slot].
 | ||||
| //
 | ||||
| // The memory for the returned string is owned by Wren. You can inspect it
 | ||||
| // while in your foreign method, but cannot keep a pointer to it after the
 | ||||
| // function returns, since the garbage collector may reclaim it.
 | ||||
| //
 | ||||
| // Returns a pointer to the first byte of the array and fill [length] with the
 | ||||
| // number of bytes in the array.
 | ||||
| //
 | ||||
| // It is an error to call this if the slot does not contain a string.
 | ||||
| const char* wrenGetSlotBytes(WrenVM* vm, int slot, int* length); | ||||
| 
 | ||||
| // Reads a number from [slot].
 | ||||
| //
 | ||||
| // It is an error to call this if the slot does not contain a number.
 | ||||
| double wrenGetSlotDouble(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Reads a foreign object from [slot] and returns a pointer to the foreign data
 | ||||
| // stored with it.
 | ||||
| //
 | ||||
| // It is an error to call this if the slot does not contain an instance of a
 | ||||
| // foreign class.
 | ||||
| void* wrenGetSlotForeign(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Reads a string from [slot].
 | ||||
| //
 | ||||
| // The memory for the returned string is owned by Wren. You can inspect it
 | ||||
| // while in your foreign method, but cannot keep a pointer to it after the
 | ||||
| // function returns, since the garbage collector may reclaim it.
 | ||||
| //
 | ||||
| // It is an error to call this if the slot does not contain a string.
 | ||||
| const char* wrenGetSlotString(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Creates a handle for the value stored in [slot].
 | ||||
| //
 | ||||
| // This will prevent the object that is referred to from being garbage collected
 | ||||
| // until the handle is released by calling [wrenReleaseHandle()].
 | ||||
| WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Stores the boolean [value] in [slot].
 | ||||
| void wrenSetSlotBool(WrenVM* vm, int slot, bool value); | ||||
| 
 | ||||
| // Stores the array [length] of [bytes] in [slot].
 | ||||
| //
 | ||||
| // The bytes are copied to a new string within Wren's heap, so you can free
 | ||||
| // memory used by them after this is called.
 | ||||
| void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); | ||||
| 
 | ||||
| // Stores the numeric [value] in [slot].
 | ||||
| void wrenSetSlotDouble(WrenVM* vm, int slot, double value); | ||||
| 
 | ||||
| // Creates a new instance of the foreign class stored in [classSlot] with [size]
 | ||||
| // bytes of raw storage and places the resulting object in [slot].
 | ||||
| //
 | ||||
| // This does not invoke the foreign class's constructor on the new instance. If
 | ||||
| // you need that to happen, call the constructor from Wren, which will then
 | ||||
| // call the allocator foreign method. In there, call this to create the object
 | ||||
| // and then the constructor will be invoked when the allocator returns.
 | ||||
| //
 | ||||
| // Returns a pointer to the foreign object's data.
 | ||||
| void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size); | ||||
| 
 | ||||
| // Stores a new empty list in [slot].
 | ||||
| void wrenSetSlotNewList(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Stores null in [slot].
 | ||||
| void wrenSetSlotNull(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Stores the string [text] in [slot].
 | ||||
| //
 | ||||
| // The [text] is copied to a new string within Wren's heap, so you can free
 | ||||
| // memory used by it after this is called. The length is calculated using
 | ||||
| // [strlen()]. If the string may contain any null bytes in the middle, then you
 | ||||
| // should use [wrenSetSlotBytes()] instead.
 | ||||
| void wrenSetSlotString(WrenVM* vm, int slot, const char* text); | ||||
| 
 | ||||
| // Stores the value captured in [handle] in [slot].
 | ||||
| //
 | ||||
| // This does not release the handle for the value.
 | ||||
| void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle); | ||||
| 
 | ||||
| // Returns the number of elements in the list stored in [slot].
 | ||||
| int wrenGetListCount(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Reads element [index] from the list in [listSlot] and stores it in
 | ||||
| // [elementSlot].
 | ||||
| void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot); | ||||
| 
 | ||||
| // Takes the value stored at [elementSlot] and inserts it into the list stored
 | ||||
| // at [listSlot] at [index].
 | ||||
| //
 | ||||
| // As in Wren, negative indexes can be used to insert from the end. To append
 | ||||
| // an element, use `-1` for the index.
 | ||||
| void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); | ||||
| 
 | ||||
| // Looks up the top level variable with [name] in resolved [module] and stores
 | ||||
| // it in [slot].
 | ||||
| void wrenGetVariable(WrenVM* vm, const char* module, const char* name, | ||||
|                      int slot); | ||||
| 
 | ||||
| // Sets the current fiber to be aborted, and uses the value in [slot] as the
 | ||||
| // runtime error object.
 | ||||
| void wrenAbortFiber(WrenVM* vm, int slot); | ||||
| 
 | ||||
| // Returns the user data associated with the WrenVM.
 | ||||
| void* wrenGetUserData(WrenVM* vm); | ||||
| 
 | ||||
| // Sets user data associated with the WrenVM.
 | ||||
| void wrenSetUserData(WrenVM* vm, void* userData); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										11
									
								
								src/logic/wren/include/wren.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/logic/wren/include/wren.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| #ifndef wren_hpp | ||||
| #define wren_hpp | ||||
| 
 | ||||
| // This is a convenience header for users that want to compile Wren as C and
 | ||||
| // link to it from a C++ application.
 | ||||
| 
 | ||||
| extern "C" { | ||||
|   #include "wren.h" | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										600
									
								
								src/logic/wren/module/io.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										600
									
								
								src/logic/wren/module/io.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,600 @@ | |||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "uv.h" | ||||
| 
 | ||||
| #include "scheduler.h" | ||||
| #include "stat.h" | ||||
| #include "vm.h" | ||||
| #include "wren.h" | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <fcntl.h> | ||||
| 
 | ||||
| typedef struct sFileRequestData | ||||
| { | ||||
|   WrenHandle* fiber; | ||||
|   uv_buf_t buffer; | ||||
| } FileRequestData; | ||||
| 
 | ||||
| static const int stdinDescriptor = 0; | ||||
| 
 | ||||
| // Handle to the Stat class object.
 | ||||
| static WrenHandle* statClass = NULL; | ||||
| 
 | ||||
| // Handle to the Stdin class object.
 | ||||
| static WrenHandle* stdinClass = NULL; | ||||
| 
 | ||||
| // Handle to an onData_() method call. Called when libuv provides data on stdin.
 | ||||
| static WrenHandle* stdinOnData = NULL; | ||||
| 
 | ||||
| // The stream used to read from stdin. Initialized on the first read.
 | ||||
| static uv_stream_t* stdinStream = NULL; | ||||
| 
 | ||||
| // True if stdin has been set to raw mode.
 | ||||
| static bool isStdinRaw = false; | ||||
| 
 | ||||
| // Frees all resources related to stdin.
 | ||||
| static void shutdownStdin() | ||||
| { | ||||
|   if (stdinStream != NULL) | ||||
|   { | ||||
|     uv_tty_reset_mode(); | ||||
|     uv_close((uv_handle_t*)stdinStream, NULL); | ||||
|     free(stdinStream); | ||||
|     stdinStream = NULL; | ||||
|   } | ||||
|    | ||||
|   if (stdinClass != NULL) | ||||
|   { | ||||
|     wrenReleaseHandle(getVM(), stdinClass); | ||||
|     stdinClass = NULL; | ||||
|   } | ||||
|    | ||||
|   if (stdinOnData != NULL) | ||||
|   { | ||||
|     wrenReleaseHandle(getVM(), stdinOnData); | ||||
|     stdinOnData = NULL; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void ioShutdown() | ||||
| { | ||||
|   shutdownStdin(); | ||||
|    | ||||
|   if (statClass != NULL) | ||||
|   { | ||||
|     wrenReleaseHandle(getVM(), statClass); | ||||
|     statClass = NULL; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // If [request] failed with an error, sends the runtime error to the VM and
 | ||||
| // frees the request.
 | ||||
| //
 | ||||
| // Returns true if an error was reported.
 | ||||
| static bool handleRequestError(uv_fs_t* request) | ||||
| { | ||||
|   if (request->result >= 0) return false; | ||||
| 
 | ||||
|   FileRequestData* data = (FileRequestData*)request->data; | ||||
|   WrenHandle* fiber = (WrenHandle*)data->fiber; | ||||
|    | ||||
|   int error = (int)request->result; | ||||
|   free(data); | ||||
|   uv_fs_req_cleanup(request); | ||||
|   free(request); | ||||
|    | ||||
|   schedulerResumeError(fiber, uv_strerror(error)); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| // Allocates a new request that resumes [fiber] when it completes.
 | ||||
| uv_fs_t* createRequest(WrenHandle* fiber) | ||||
| { | ||||
|   uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t)); | ||||
|    | ||||
|   FileRequestData* data = (FileRequestData*)malloc(sizeof(FileRequestData)); | ||||
|   data->fiber = fiber; | ||||
|    | ||||
|   request->data = data; | ||||
|   return request; | ||||
| } | ||||
| 
 | ||||
| // Releases resources used by [request].
 | ||||
| //
 | ||||
| // Returns the fiber that should be resumed after [request] completes.
 | ||||
| WrenHandle* freeRequest(uv_fs_t* request) | ||||
| { | ||||
|   FileRequestData* data = (FileRequestData*)request->data; | ||||
|   WrenHandle* fiber = data->fiber; | ||||
|    | ||||
|   free(data); | ||||
|   uv_fs_req_cleanup(request); | ||||
|   free(request); | ||||
|    | ||||
|   return fiber; | ||||
| } | ||||
| 
 | ||||
| static void directoryListCallback(uv_fs_t* request) | ||||
| { | ||||
|   if (handleRequestError(request)) return; | ||||
| 
 | ||||
|   uv_dirent_t entry; | ||||
| 
 | ||||
|   WrenVM* vm = getVM(); | ||||
|   wrenEnsureSlots(vm, 3); | ||||
|   wrenSetSlotNewList(vm, 2); | ||||
|    | ||||
|   while (uv_fs_scandir_next(request, &entry) != UV_EOF) | ||||
|   { | ||||
|     wrenSetSlotString(vm, 1, entry.name); | ||||
|     wrenInsertInList(vm, 2, -1, 1); | ||||
|   } | ||||
|    | ||||
|   schedulerResume(freeRequest(request), true); | ||||
|   schedulerFinishResume(); | ||||
| } | ||||
| 
 | ||||
| void directoryList(WrenVM* vm) | ||||
| { | ||||
|   const char* path = wrenGetSlotString(vm, 1); | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 2)); | ||||
|    | ||||
|   // TODO: Check return.
 | ||||
|   uv_fs_scandir(getLoop(), request, path, 0, directoryListCallback); | ||||
| } | ||||
| 
 | ||||
| void fileAllocate(WrenVM* vm) | ||||
| { | ||||
|   // Store the file descriptor in the foreign data, so that we can get to it
 | ||||
|   // in the finalizer.
 | ||||
|   int* fd = (int*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(int)); | ||||
|   *fd = (int)wrenGetSlotDouble(vm, 1); | ||||
| } | ||||
| 
 | ||||
| void fileFinalize(void* data) | ||||
| { | ||||
|   int fd = *(int*)data; | ||||
|    | ||||
|   // Already closed.
 | ||||
|   if (fd == -1) return; | ||||
|    | ||||
|   uv_fs_t request; | ||||
|   uv_fs_close(getLoop(), &request, fd, NULL); | ||||
|   uv_fs_req_cleanup(&request); | ||||
| } | ||||
| 
 | ||||
| static void fileDeleteCallback(uv_fs_t* request) | ||||
| { | ||||
|   if (handleRequestError(request)) return; | ||||
|   schedulerResume(freeRequest(request), false); | ||||
| } | ||||
| 
 | ||||
| void fileDelete(WrenVM* vm) | ||||
| { | ||||
|   const char* path = wrenGetSlotString(vm, 1); | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 2)); | ||||
|    | ||||
|   // TODO: Check return.
 | ||||
|   uv_fs_unlink(getLoop(), request, path, fileDeleteCallback); | ||||
| } | ||||
| 
 | ||||
| static void fileOpenCallback(uv_fs_t* request) | ||||
| { | ||||
|   if (handleRequestError(request)) return; | ||||
|    | ||||
|   double fd = (double)request->result; | ||||
|   schedulerResume(freeRequest(request), true); | ||||
|   wrenSetSlotDouble(getVM(), 2, fd); | ||||
|   schedulerFinishResume(); | ||||
| } | ||||
| 
 | ||||
| // The UNIX file flags have specified names but not values. So we define our
 | ||||
| // own values in FileFlags and remap them to the host OS's values here.
 | ||||
| static int mapFileFlags(int flags) | ||||
| { | ||||
|   int result = 0; | ||||
|    | ||||
|   // Note: These must be kept in sync with FileFlags in io.wren.
 | ||||
|   if (flags & 0x01) result |= O_RDONLY; | ||||
|   if (flags & 0x02) result |= O_WRONLY; | ||||
|   if (flags & 0x04) result |= O_RDWR; | ||||
|   if (flags & 0x08) result |= O_SYNC; | ||||
|   if (flags & 0x10) result |= O_CREAT; | ||||
|   if (flags & 0x20) result |= O_TRUNC; | ||||
|   if (flags & 0x40) result |= O_EXCL; | ||||
|    | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| void fileOpen(WrenVM* vm) | ||||
| { | ||||
|   const char* path = wrenGetSlotString(vm, 1); | ||||
|   int flags = (int)wrenGetSlotDouble(vm, 2); | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 3)); | ||||
| 
 | ||||
|   // TODO: Allow controlling access.
 | ||||
|   uv_fs_open(getLoop(), request, path, mapFileFlags(flags), S_IRUSR | S_IWUSR, | ||||
|              fileOpenCallback); | ||||
| } | ||||
| 
 | ||||
| // Called by libuv when the stat call for size completes.
 | ||||
| static void fileSizeCallback(uv_fs_t* request) | ||||
| { | ||||
|   if (handleRequestError(request)) return; | ||||
| 
 | ||||
|   double size = (double)request->statbuf.st_size; | ||||
|   schedulerResume(freeRequest(request), true); | ||||
|   wrenSetSlotDouble(getVM(), 2, size); | ||||
|   schedulerFinishResume(); | ||||
| } | ||||
| 
 | ||||
| void fileSizePath(WrenVM* vm) | ||||
| { | ||||
|   const char* path = wrenGetSlotString(vm, 1); | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 2)); | ||||
|   uv_fs_stat(getLoop(), request, path, fileSizeCallback); | ||||
| } | ||||
| 
 | ||||
| static void fileCloseCallback(uv_fs_t* request) | ||||
| { | ||||
|   if (handleRequestError(request)) return; | ||||
| 
 | ||||
|   schedulerResume(freeRequest(request), false); | ||||
| } | ||||
| 
 | ||||
| void fileClose(WrenVM* vm) | ||||
| { | ||||
|   int* foreign = (int*)wrenGetSlotForeign(vm, 0); | ||||
|   int fd = *foreign; | ||||
| 
 | ||||
|   // If it's already closed, we're done.
 | ||||
|   if (fd == -1) | ||||
|   { | ||||
|     wrenSetSlotBool(vm, 0, true); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // Mark it closed immediately.
 | ||||
|   *foreign = -1; | ||||
| 
 | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 1)); | ||||
|   uv_fs_close(getLoop(), request, fd, fileCloseCallback); | ||||
|   wrenSetSlotBool(vm, 0, false); | ||||
| } | ||||
| 
 | ||||
| void fileDescriptor(WrenVM* vm) | ||||
| { | ||||
|   int* foreign = (int*)wrenGetSlotForeign(vm, 0); | ||||
|   int fd = *foreign; | ||||
|   wrenSetSlotDouble(vm, 0, fd); | ||||
| } | ||||
| 
 | ||||
| static void fileReadBytesCallback(uv_fs_t* request) | ||||
| { | ||||
|   if (handleRequestError(request)) return; | ||||
| 
 | ||||
|   FileRequestData* data = (FileRequestData*)request->data; | ||||
|   uv_buf_t buffer = data->buffer; | ||||
|   size_t count = request->result; | ||||
| 
 | ||||
|   // TODO: Having to copy the bytes here is a drag. It would be good if Wren's
 | ||||
|   // embedding API supported a way to *give* it bytes that were previously
 | ||||
|   // allocated using Wren's own allocator.
 | ||||
|   schedulerResume(freeRequest(request), true); | ||||
|   wrenSetSlotBytes(getVM(), 2, buffer.base, count); | ||||
|   schedulerFinishResume(); | ||||
| 
 | ||||
|   // TODO: Likewise, freeing this after we resume is lame.
 | ||||
|   free(buffer.base); | ||||
| } | ||||
| 
 | ||||
| void fileReadBytes(WrenVM* vm) | ||||
| { | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 3)); | ||||
| 
 | ||||
|   int fd = *(int*)wrenGetSlotForeign(vm, 0); | ||||
|   // TODO: Assert fd != -1.
 | ||||
| 
 | ||||
|   FileRequestData* data = (FileRequestData*)request->data; | ||||
|   size_t length = (size_t)wrenGetSlotDouble(vm, 1); | ||||
|   size_t offset = (size_t)wrenGetSlotDouble(vm, 2); | ||||
| 
 | ||||
|   data->buffer.len = length; | ||||
|   data->buffer.base = (char*)malloc(length); | ||||
| 
 | ||||
|   uv_fs_read(getLoop(), request, fd, &data->buffer, 1, offset, | ||||
|              fileReadBytesCallback); | ||||
| } | ||||
| 
 | ||||
| static void realPathCallback(uv_fs_t* request) | ||||
| { | ||||
|   if (handleRequestError(request)) return; | ||||
|    | ||||
|   wrenEnsureSlots(getVM(), 3); | ||||
|   wrenSetSlotString(getVM(), 2, (char*)request->ptr); | ||||
|   schedulerResume(freeRequest(request), true); | ||||
|   schedulerFinishResume(); | ||||
| } | ||||
| 
 | ||||
| void fileRealPath(WrenVM* vm) | ||||
| { | ||||
|   const char* path = wrenGetSlotString(vm, 1); | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 2)); | ||||
|   uv_fs_realpath(getLoop(), request, path, realPathCallback); | ||||
| } | ||||
| 
 | ||||
| // Called by libuv when the stat call completes.
 | ||||
| static void statCallback(uv_fs_t* request) | ||||
| { | ||||
|   if (handleRequestError(request)) return; | ||||
|    | ||||
|   WrenVM* vm = getVM(); | ||||
|   wrenEnsureSlots(vm, 3); | ||||
|    | ||||
|   // Get a handle to the Stat class. We'll hang on to this so we don't have to
 | ||||
|   // look it up by name every time.
 | ||||
|   if (statClass == NULL) | ||||
|   { | ||||
|     wrenGetVariable(vm, "io", "Stat", 0); | ||||
|     statClass = wrenGetSlotHandle(vm, 0); | ||||
|   } | ||||
|    | ||||
|   // Create a foreign Stat object to store the stat struct.
 | ||||
|   wrenSetSlotHandle(vm, 2, statClass); | ||||
|   wrenSetSlotNewForeign(vm, 2, 2, sizeof(uv_stat_t)); | ||||
|    | ||||
|   // Copy the stat data.
 | ||||
|   uv_stat_t* data = (uv_stat_t*)wrenGetSlotForeign(vm, 2); | ||||
|   *data = request->statbuf; | ||||
|    | ||||
|   schedulerResume(freeRequest(request), true); | ||||
|   schedulerFinishResume(); | ||||
| } | ||||
| 
 | ||||
| void fileStat(WrenVM* vm) | ||||
| { | ||||
|   int fd = *(int*)wrenGetSlotForeign(vm, 0); | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 1)); | ||||
|   uv_fs_fstat(getLoop(), request, fd, statCallback); | ||||
| } | ||||
| 
 | ||||
| void fileSize(WrenVM* vm) | ||||
| { | ||||
|   int fd = *(int*)wrenGetSlotForeign(vm, 0); | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 1)); | ||||
|   uv_fs_fstat(getLoop(), request, fd, fileSizeCallback); | ||||
| } | ||||
| 
 | ||||
| static void fileWriteBytesCallback(uv_fs_t* request) | ||||
| { | ||||
|   if (handleRequestError(request)) return; | ||||
|   | ||||
|   FileRequestData* data = (FileRequestData*)request->data; | ||||
|   free(data->buffer.base); | ||||
| 
 | ||||
|   schedulerResume(freeRequest(request), false); | ||||
| } | ||||
| 
 | ||||
| void fileWriteBytes(WrenVM* vm) | ||||
| { | ||||
|   int fd = *(int*)wrenGetSlotForeign(vm, 0); | ||||
|   int length; | ||||
|   const char* bytes = wrenGetSlotBytes(vm, 1, &length); | ||||
|   size_t offset = (size_t)wrenGetSlotDouble(vm, 2); | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 3)); | ||||
|    | ||||
|   FileRequestData* data = (FileRequestData*)request->data; | ||||
| 
 | ||||
|   data->buffer.len = length; | ||||
|   // TODO: Instead of copying, just create a WrenHandle for the byte string and
 | ||||
|   // hold on to it in the request until the write is done.
 | ||||
|   // TODO: Handle allocation failure.
 | ||||
|   data->buffer.base = (char*)malloc(length); | ||||
|   memcpy(data->buffer.base, bytes, length); | ||||
| 
 | ||||
|   uv_fs_write(getLoop(), request, fd, &data->buffer, 1, offset, | ||||
|               fileWriteBytesCallback); | ||||
| } | ||||
| 
 | ||||
| void statPath(WrenVM* vm) | ||||
| { | ||||
|   const char* path = wrenGetSlotString(vm, 1); | ||||
|   uv_fs_t* request = createRequest(wrenGetSlotHandle(vm, 2)); | ||||
|   uv_fs_stat(getLoop(), request, path, statCallback); | ||||
| } | ||||
| 
 | ||||
| void statBlockCount(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_blocks); | ||||
| } | ||||
| 
 | ||||
| void statBlockSize(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_blksize); | ||||
| } | ||||
| 
 | ||||
| void statDevice(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_dev); | ||||
| } | ||||
| 
 | ||||
| void statGroup(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_gid); | ||||
| } | ||||
| 
 | ||||
| void statInode(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_ino); | ||||
| } | ||||
| 
 | ||||
| void statLinkCount(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_nlink); | ||||
| } | ||||
| 
 | ||||
| void statMode(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_mode); | ||||
| } | ||||
| 
 | ||||
| void statSize(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_size); | ||||
| } | ||||
| 
 | ||||
| void statSpecialDevice(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_rdev); | ||||
| } | ||||
| 
 | ||||
| void statUser(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotDouble(vm, 0, (double)stat->st_uid); | ||||
| } | ||||
| 
 | ||||
| void statIsDirectory(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotBool(vm, 0, S_ISDIR(stat->st_mode)); | ||||
| } | ||||
| 
 | ||||
| void statIsFile(WrenVM* vm) | ||||
| { | ||||
|   uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0); | ||||
|   wrenSetSlotBool(vm, 0, S_ISREG(stat->st_mode)); | ||||
| } | ||||
| 
 | ||||
| // Sets up the stdin stream if not already initialized.
 | ||||
| static void initStdin() | ||||
| { | ||||
|   if (stdinStream == NULL) | ||||
|   { | ||||
|     if (uv_guess_handle(stdinDescriptor) == UV_TTY) | ||||
|     { | ||||
|       // stdin is connected to a terminal.
 | ||||
|       uv_tty_t* handle = (uv_tty_t*)malloc(sizeof(uv_tty_t)); | ||||
|       uv_tty_init(getLoop(), handle, stdinDescriptor, true); | ||||
|        | ||||
|       stdinStream = (uv_stream_t*)handle; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       // stdin is a pipe or a file.
 | ||||
|       uv_pipe_t* handle = (uv_pipe_t*)malloc(sizeof(uv_pipe_t)); | ||||
|       uv_pipe_init(getLoop(), handle, false); | ||||
|       uv_pipe_open(handle, stdinDescriptor); | ||||
|       stdinStream = (uv_stream_t*)handle; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void stdinIsRaw(WrenVM* vm) | ||||
| { | ||||
|   wrenSetSlotBool(vm, 0, isStdinRaw); | ||||
| } | ||||
| 
 | ||||
| void stdinIsRawSet(WrenVM* vm) | ||||
| { | ||||
|   initStdin(); | ||||
|    | ||||
|   isStdinRaw = wrenGetSlotBool(vm, 1); | ||||
|    | ||||
|   if (uv_guess_handle(stdinDescriptor) == UV_TTY) | ||||
|   { | ||||
|     uv_tty_t* handle = (uv_tty_t*)stdinStream; | ||||
|     uv_tty_set_mode(handle, isStdinRaw ? UV_TTY_MODE_RAW : UV_TTY_MODE_NORMAL); | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     // Can't set raw mode when not talking to a TTY.
 | ||||
|     // TODO: Make this a runtime error?
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void stdinIsTerminal(WrenVM* vm) | ||||
| { | ||||
|   initStdin(); | ||||
|   wrenSetSlotBool(vm, 0, uv_guess_handle(stdinDescriptor) == UV_TTY); | ||||
| } | ||||
| 
 | ||||
| void stdoutFlush(WrenVM* vm) | ||||
| { | ||||
|   fflush(stdout); | ||||
|   wrenSetSlotNull(vm, 0); | ||||
| } | ||||
| 
 | ||||
| static void allocCallback(uv_handle_t* handle, size_t suggestedSize, | ||||
|                           uv_buf_t* buf) | ||||
| { | ||||
|   // TODO: Handle allocation failure.
 | ||||
|   buf->base = (char*)malloc(suggestedSize); | ||||
|   buf->len = suggestedSize; | ||||
| } | ||||
| 
 | ||||
| static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead, | ||||
|                               const uv_buf_t* buffer) | ||||
| { | ||||
|   WrenVM* vm = getVM(); | ||||
|    | ||||
|   if (stdinClass == NULL) | ||||
|   { | ||||
|     wrenEnsureSlots(vm, 1); | ||||
|     wrenGetVariable(vm, "io", "Stdin", 0); | ||||
|     stdinClass = wrenGetSlotHandle(vm, 0); | ||||
|   } | ||||
|    | ||||
|   if (stdinOnData == NULL) | ||||
|   { | ||||
|     stdinOnData = wrenMakeCallHandle(vm, "onData_(_)"); | ||||
|   } | ||||
|    | ||||
|   // If stdin was closed, send null to let io.wren know.
 | ||||
|   if (numRead == UV_EOF) | ||||
|   { | ||||
|     wrenEnsureSlots(vm, 2); | ||||
|     wrenSetSlotHandle(vm, 0, stdinClass); | ||||
|     wrenSetSlotNull(vm, 1); | ||||
|     wrenCall(vm, stdinOnData); | ||||
|      | ||||
|     shutdownStdin(); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // TODO: Handle other errors.
 | ||||
| 
 | ||||
|   // TODO: Having to copy the bytes here is a drag. It would be good if Wren's
 | ||||
|   // embedding API supported a way to *give* it bytes that were previously
 | ||||
|   // allocated using Wren's own allocator.
 | ||||
|   wrenEnsureSlots(vm, 2); | ||||
|   wrenSetSlotHandle(vm, 0, stdinClass); | ||||
|   wrenSetSlotBytes(vm, 1, buffer->base, numRead); | ||||
|   wrenCall(vm, stdinOnData); | ||||
| 
 | ||||
|   // TODO: Likewise, freeing this after we resume is lame.
 | ||||
|   free(buffer->base); | ||||
| } | ||||
| 
 | ||||
| void stdinReadStart(WrenVM* vm) | ||||
| { | ||||
|   initStdin(); | ||||
|   uv_read_start(stdinStream, allocCallback, stdinReadCallback); | ||||
|   // TODO: Check return.
 | ||||
| } | ||||
| 
 | ||||
| void stdinReadStop(WrenVM* vm) | ||||
| { | ||||
|   uv_read_stop(stdinStream); | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/logic/wren/module/io.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/logic/wren/module/io.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| #ifndef io_h | ||||
| #define io_h | ||||
| 
 | ||||
| #include "wren.h" | ||||
| 
 | ||||
| // Frees up any pending resources in use by the IO module.
 | ||||
| //
 | ||||
| // In particular, this closes down the stdin stream.
 | ||||
| void ioShutdown(); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										303
									
								
								src/logic/wren/module/io.wren
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								src/logic/wren/module/io.wren
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,303 @@ | |||
| import "scheduler" for Scheduler | ||||
| 
 | ||||
| class Directory { | ||||
|   // TODO: Copied from File. Figure out good way to share this. | ||||
|   static ensurePath_(path) { | ||||
|     if (!(path is String)) Fiber.abort("Path must be a string.") | ||||
|   } | ||||
| 
 | ||||
|   static exists(path) { | ||||
|     ensurePath_(path) | ||||
|     var stat | ||||
|     Fiber.new { | ||||
|       stat = Stat.path(path) | ||||
|     }.try() | ||||
| 
 | ||||
|     // If we can't stat it, there's nothing there. | ||||
|     if (stat == null) return false | ||||
|     return stat.isDirectory | ||||
|   } | ||||
| 
 | ||||
|   static list(path) { | ||||
|     ensurePath_(path) | ||||
|     list_(path, Fiber.current) | ||||
|     return Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   foreign static list_(path, fiber) | ||||
| } | ||||
| 
 | ||||
| foreign class File { | ||||
|   static create(path) { | ||||
|     return openWithFlags(path, | ||||
|         FileFlags.writeOnly | | ||||
|         FileFlags.create | | ||||
|         FileFlags.truncate) | ||||
|   } | ||||
| 
 | ||||
|   static create(path, fn) { | ||||
|     return openWithFlags(path, | ||||
|         FileFlags.writeOnly | | ||||
|         FileFlags.create | | ||||
|         FileFlags.truncate, fn) | ||||
|   } | ||||
| 
 | ||||
|   static delete(path) { | ||||
|     ensurePath_(path) | ||||
|     delete_(path, Fiber.current) | ||||
|     return Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   static exists(path) { | ||||
|     ensurePath_(path) | ||||
|     var stat | ||||
|     Fiber.new { | ||||
|       stat = Stat.path(path) | ||||
|     }.try() | ||||
| 
 | ||||
|     // If we can't stat it, there's nothing there. | ||||
|     if (stat == null) return false | ||||
|     return stat.isFile | ||||
|   } | ||||
| 
 | ||||
|   static open(path) { openWithFlags(path, FileFlags.readOnly) } | ||||
| 
 | ||||
|   static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) } | ||||
| 
 | ||||
|   // TODO: Add named parameters and then call this "open(_,flags:_)"? | ||||
|   // TODO: Test. | ||||
|   static openWithFlags(path, flags) { | ||||
|     ensurePath_(path) | ||||
|     ensureInt_(flags, "Flags") | ||||
|     open_(path, flags, Fiber.current) | ||||
|     var fd = Scheduler.runNextScheduled_() | ||||
|     return new_(fd) | ||||
|   } | ||||
| 
 | ||||
|   static openWithFlags(path, flags, fn) { | ||||
|     var file = openWithFlags(path, flags) | ||||
|     var fiber = Fiber.new { fn.call(file) } | ||||
| 
 | ||||
|     // Poor man's finally. Can we make this more elegant? | ||||
|     var result = fiber.try() | ||||
|     file.close() | ||||
| 
 | ||||
|     // TODO: Want something like rethrow since now the callstack ends here. :( | ||||
|     if (fiber.error != null) Fiber.abort(fiber.error) | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   static read(path) { | ||||
|     return File.open(path) {|file| file.readBytes(file.size) } | ||||
|   } | ||||
| 
 | ||||
|   // TODO: This works for directories too, so putting it on File is kind of | ||||
|   // lame. Consider reorganizing these classes some. | ||||
|   static realPath(path) { | ||||
|     ensurePath_(path) | ||||
|     realPath_(path, Fiber.current) | ||||
|     return Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   static size(path) { | ||||
|     ensurePath_(path) | ||||
|     sizePath_(path, Fiber.current) | ||||
|     return Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   construct new_(fd) {} | ||||
| 
 | ||||
|   close() { | ||||
|     if (close_(Fiber.current)) return | ||||
|     Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   foreign descriptor | ||||
| 
 | ||||
|   isOpen { descriptor != -1 } | ||||
| 
 | ||||
|   size { | ||||
|     ensureOpen_() | ||||
|     size_(Fiber.current) | ||||
|     return Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   stat { | ||||
|     ensureOpen_() | ||||
|     stat_(Fiber.current) | ||||
|     return Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   readBytes(count) { readBytes(count, 0) } | ||||
| 
 | ||||
|   readBytes(count, offset) { | ||||
|     ensureOpen_() | ||||
|     File.ensureInt_(count, "Count") | ||||
|     File.ensureInt_(offset, "Offset") | ||||
| 
 | ||||
|     readBytes_(count, offset, Fiber.current) | ||||
|     return Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   writeBytes(bytes) { writeBytes(bytes, size) } | ||||
| 
 | ||||
|   writeBytes(bytes, offset) { | ||||
|     ensureOpen_() | ||||
|     if (!(bytes is String)) Fiber.abort("Bytes must be a string.") | ||||
|     File.ensureInt_(offset, "Offset") | ||||
| 
 | ||||
|     writeBytes_(bytes, offset, Fiber.current) | ||||
|     return Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   ensureOpen_() { | ||||
|     if (!isOpen) Fiber.abort("File is not open.") | ||||
|   } | ||||
| 
 | ||||
|   static ensurePath_(path) { | ||||
|     if (!(path is String)) Fiber.abort("Path must be a string.") | ||||
|   } | ||||
| 
 | ||||
|   static ensureInt_(value, name) { | ||||
|     if (!(value is Num)) Fiber.abort("%(name) must be an integer.") | ||||
|     if (!value.isInteger) Fiber.abort("%(name) must be an integer.") | ||||
|     if (value < 0) Fiber.abort("%(name) cannot be negative.") | ||||
|   } | ||||
| 
 | ||||
|   foreign static delete_(path, fiber) | ||||
|   foreign static open_(path, flags, fiber) | ||||
|   foreign static realPath_(path, fiber) | ||||
|   foreign static sizePath_(path, fiber) | ||||
| 
 | ||||
|   foreign close_(fiber) | ||||
|   foreign readBytes_(count, offset, fiber) | ||||
|   foreign size_(fiber) | ||||
|   foreign stat_(fiber) | ||||
|   foreign writeBytes_(bytes, offset, fiber) | ||||
| } | ||||
| 
 | ||||
| class FileFlags { | ||||
|   // Note: These must be kept in sync with mapFileFlags() in io.c. | ||||
| 
 | ||||
|   static readOnly  { 0x01 } | ||||
|   static writeOnly { 0x02 } | ||||
|   static readWrite { 0x04 } | ||||
|   static sync      { 0x08 } | ||||
|   static create    { 0x10 } | ||||
|   static truncate  { 0x20 } | ||||
|   static exclusive { 0x40 } | ||||
| } | ||||
| 
 | ||||
| foreign class Stat { | ||||
|   static path(path) { | ||||
|     if (!(path is String)) Fiber.abort("Path must be a string.") | ||||
| 
 | ||||
|     path_(path, Fiber.current) | ||||
|     return Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   foreign static path_(path, fiber) | ||||
| 
 | ||||
|   foreign blockCount | ||||
|   foreign blockSize | ||||
|   foreign device | ||||
|   foreign group | ||||
|   foreign inode | ||||
|   foreign linkCount | ||||
|   foreign mode | ||||
|   foreign size | ||||
|   foreign specialDevice | ||||
|   foreign user | ||||
| 
 | ||||
|   foreign isFile | ||||
|   foreign isDirectory | ||||
|   // TODO: Other mode checks. | ||||
| } | ||||
| 
 | ||||
| class Stdin { | ||||
|   foreign static isRaw | ||||
|   foreign static isRaw=(value) | ||||
|   foreign static isTerminal | ||||
| 
 | ||||
|   static readByte() { | ||||
|     return read_ { | ||||
|       // Peel off the first byte. | ||||
|       var byte = __buffered.bytes[0] | ||||
|       __buffered = __buffered[1..-1] | ||||
|       return byte | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static readLine() { | ||||
|     return read_ { | ||||
|       // TODO: Handle Windows line separators. | ||||
|       var lineSeparator = __buffered.indexOf("\n") | ||||
|       if (lineSeparator == -1) return null | ||||
| 
 | ||||
|       // Split the line at the separator. | ||||
|       var line = __buffered[0...lineSeparator] | ||||
|       __buffered = __buffered[lineSeparator + 1..-1] | ||||
|       return line | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static read_(handleData) { | ||||
|     // See if we're already buffered enough to immediately produce a result. | ||||
|     if (__buffered != null && !__buffered.isEmpty) { | ||||
|       var result = handleData.call() | ||||
|       if (result != null) return result | ||||
|     } | ||||
| 
 | ||||
|     if (__isClosed == true) Fiber.abort("Stdin was closed.") | ||||
| 
 | ||||
|     // Otherwise, we need to wait for input to come in. | ||||
|     __handleData = handleData | ||||
| 
 | ||||
|     // TODO: Error if other fiber is already waiting. | ||||
|     readStart_() | ||||
| 
 | ||||
|     __waitingFiber = Fiber.current | ||||
|     var result = Scheduler.runNextScheduled_() | ||||
| 
 | ||||
|     readStop_() | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   static onData_(data) { | ||||
|     // If data is null, it means stdin just closed. | ||||
|     if (data == null) { | ||||
|       __isClosed = true | ||||
|       readStop_() | ||||
| 
 | ||||
|       if (__buffered != null) { | ||||
|         // TODO: Is this correct for readByte()? | ||||
|         // Emit the last remaining bytes. | ||||
|         var result = __buffered | ||||
|         __buffered = null | ||||
|         __waitingFiber.transfer(result) | ||||
|       } else { | ||||
|         __waitingFiber.transferError("Stdin was closed.") | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // Append to the buffer. | ||||
|     if (__buffered == null) { | ||||
|       __buffered = data | ||||
|     } else { | ||||
|       // TODO: Instead of concatenating strings each time, it's probably faster | ||||
|       // to keep a list of buffers and flatten lazily. | ||||
|       __buffered = __buffered + data | ||||
|     } | ||||
| 
 | ||||
|     // Ask the data handler if we have a complete result now. | ||||
|     var result = __handleData.call() | ||||
|     if (result != null) __waitingFiber.transfer(result) | ||||
|   } | ||||
| 
 | ||||
|   foreign static readStart_() | ||||
|   foreign static readStop_() | ||||
| } | ||||
| 
 | ||||
| class Stdout { | ||||
|   foreign static flush() | ||||
| } | ||||
							
								
								
									
										305
									
								
								src/logic/wren/module/io.wren.inc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								src/logic/wren/module/io.wren.inc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,305 @@ | |||
| // Generated automatically from src/module/io.wren. Do not edit.
 | ||||
| static const char* ioModuleSource = | ||||
| "import \"scheduler\" for Scheduler\n" | ||||
| "\n" | ||||
| "class Directory {\n" | ||||
| "  // TODO: Copied from File. Figure out good way to share this.\n" | ||||
| "  static ensurePath_(path) {\n" | ||||
| "    if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static exists(path) {\n" | ||||
| "    ensurePath_(path)\n" | ||||
| "    var stat\n" | ||||
| "    Fiber.new {\n" | ||||
| "      stat = Stat.path(path)\n" | ||||
| "    }.try()\n" | ||||
| "\n" | ||||
| "    // If we can't stat it, there's nothing there.\n" | ||||
| "    if (stat == null) return false\n" | ||||
| "    return stat.isDirectory\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static list(path) {\n" | ||||
| "    ensurePath_(path)\n" | ||||
| "    list_(path, Fiber.current)\n" | ||||
| "    return Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  foreign static list_(path, fiber)\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "foreign class File {\n" | ||||
| "  static create(path) {\n" | ||||
| "    return openWithFlags(path,\n" | ||||
| "        FileFlags.writeOnly |\n" | ||||
| "        FileFlags.create |\n" | ||||
| "        FileFlags.truncate)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static create(path, fn) {\n" | ||||
| "    return openWithFlags(path,\n" | ||||
| "        FileFlags.writeOnly |\n" | ||||
| "        FileFlags.create |\n" | ||||
| "        FileFlags.truncate, fn)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static delete(path) {\n" | ||||
| "    ensurePath_(path)\n" | ||||
| "    delete_(path, Fiber.current)\n" | ||||
| "    return Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static exists(path) {\n" | ||||
| "    ensurePath_(path)\n" | ||||
| "    var stat\n" | ||||
| "    Fiber.new {\n" | ||||
| "      stat = Stat.path(path)\n" | ||||
| "    }.try()\n" | ||||
| "\n" | ||||
| "    // If we can't stat it, there's nothing there.\n" | ||||
| "    if (stat == null) return false\n" | ||||
| "    return stat.isFile\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static open(path) { openWithFlags(path, FileFlags.readOnly) }\n" | ||||
| "\n" | ||||
| "  static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) }\n" | ||||
| "\n" | ||||
| "  // TODO: Add named parameters and then call this \"open(_,flags:_)\"?\n" | ||||
| "  // TODO: Test.\n" | ||||
| "  static openWithFlags(path, flags) {\n" | ||||
| "    ensurePath_(path)\n" | ||||
| "    ensureInt_(flags, \"Flags\")\n" | ||||
| "    open_(path, flags, Fiber.current)\n" | ||||
| "    var fd = Scheduler.runNextScheduled_()\n" | ||||
| "    return new_(fd)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static openWithFlags(path, flags, fn) {\n" | ||||
| "    var file = openWithFlags(path, flags)\n" | ||||
| "    var fiber = Fiber.new { fn.call(file) }\n" | ||||
| "\n" | ||||
| "    // Poor man's finally. Can we make this more elegant?\n" | ||||
| "    var result = fiber.try()\n" | ||||
| "    file.close()\n" | ||||
| "\n" | ||||
| "    // TODO: Want something like rethrow since now the callstack ends here. :(\n" | ||||
| "    if (fiber.error != null) Fiber.abort(fiber.error)\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static read(path) {\n" | ||||
| "    return File.open(path) {|file| file.readBytes(file.size) }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // TODO: This works for directories too, so putting it on File is kind of\n" | ||||
| "  // lame. Consider reorganizing these classes some.\n" | ||||
| "  static realPath(path) {\n" | ||||
| "    ensurePath_(path)\n" | ||||
| "    realPath_(path, Fiber.current)\n" | ||||
| "    return Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static size(path) {\n" | ||||
| "    ensurePath_(path)\n" | ||||
| "    sizePath_(path, Fiber.current)\n" | ||||
| "    return Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  construct new_(fd) {}\n" | ||||
| "\n" | ||||
| "  close() {\n" | ||||
| "    if (close_(Fiber.current)) return\n" | ||||
| "    Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  foreign descriptor\n" | ||||
| "\n" | ||||
| "  isOpen { descriptor != -1 }\n" | ||||
| "\n" | ||||
| "  size {\n" | ||||
| "    ensureOpen_()\n" | ||||
| "    size_(Fiber.current)\n" | ||||
| "    return Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  stat {\n" | ||||
| "    ensureOpen_()\n" | ||||
| "    stat_(Fiber.current)\n" | ||||
| "    return Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  readBytes(count) { readBytes(count, 0) }\n" | ||||
| "\n" | ||||
| "  readBytes(count, offset) {\n" | ||||
| "    ensureOpen_()\n" | ||||
| "    File.ensureInt_(count, \"Count\")\n" | ||||
| "    File.ensureInt_(offset, \"Offset\")\n" | ||||
| "\n" | ||||
| "    readBytes_(count, offset, Fiber.current)\n" | ||||
| "    return Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  writeBytes(bytes) { writeBytes(bytes, size) }\n" | ||||
| "\n" | ||||
| "  writeBytes(bytes, offset) {\n" | ||||
| "    ensureOpen_()\n" | ||||
| "    if (!(bytes is String)) Fiber.abort(\"Bytes must be a string.\")\n" | ||||
| "    File.ensureInt_(offset, \"Offset\")\n" | ||||
| "\n" | ||||
| "    writeBytes_(bytes, offset, Fiber.current)\n" | ||||
| "    return Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  ensureOpen_() {\n" | ||||
| "    if (!isOpen) Fiber.abort(\"File is not open.\")\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static ensurePath_(path) {\n" | ||||
| "    if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static ensureInt_(value, name) {\n" | ||||
| "    if (!(value is Num)) Fiber.abort(\"%(name) must be an integer.\")\n" | ||||
| "    if (!value.isInteger) Fiber.abort(\"%(name) must be an integer.\")\n" | ||||
| "    if (value < 0) Fiber.abort(\"%(name) cannot be negative.\")\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  foreign static delete_(path, fiber)\n" | ||||
| "  foreign static open_(path, flags, fiber)\n" | ||||
| "  foreign static realPath_(path, fiber)\n" | ||||
| "  foreign static sizePath_(path, fiber)\n" | ||||
| "\n" | ||||
| "  foreign close_(fiber)\n" | ||||
| "  foreign readBytes_(count, offset, fiber)\n" | ||||
| "  foreign size_(fiber)\n" | ||||
| "  foreign stat_(fiber)\n" | ||||
| "  foreign writeBytes_(bytes, offset, fiber)\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class FileFlags {\n" | ||||
| "  // Note: These must be kept in sync with mapFileFlags() in io.c.\n" | ||||
| "\n" | ||||
| "  static readOnly  { 0x01 }\n" | ||||
| "  static writeOnly { 0x02 }\n" | ||||
| "  static readWrite { 0x04 }\n" | ||||
| "  static sync      { 0x08 }\n" | ||||
| "  static create    { 0x10 }\n" | ||||
| "  static truncate  { 0x20 }\n" | ||||
| "  static exclusive { 0x40 }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "foreign class Stat {\n" | ||||
| "  static path(path) {\n" | ||||
| "    if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" | ||||
| "\n" | ||||
| "    path_(path, Fiber.current)\n" | ||||
| "    return Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  foreign static path_(path, fiber)\n" | ||||
| "\n" | ||||
| "  foreign blockCount\n" | ||||
| "  foreign blockSize\n" | ||||
| "  foreign device\n" | ||||
| "  foreign group\n" | ||||
| "  foreign inode\n" | ||||
| "  foreign linkCount\n" | ||||
| "  foreign mode\n" | ||||
| "  foreign size\n" | ||||
| "  foreign specialDevice\n" | ||||
| "  foreign user\n" | ||||
| "\n" | ||||
| "  foreign isFile\n" | ||||
| "  foreign isDirectory\n" | ||||
| "  // TODO: Other mode checks.\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class Stdin {\n" | ||||
| "  foreign static isRaw\n" | ||||
| "  foreign static isRaw=(value)\n" | ||||
| "  foreign static isTerminal\n" | ||||
| "\n" | ||||
| "  static readByte() {\n" | ||||
| "    return read_ {\n" | ||||
| "      // Peel off the first byte.\n" | ||||
| "      var byte = __buffered.bytes[0]\n" | ||||
| "      __buffered = __buffered[1..-1]\n" | ||||
| "      return byte\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static readLine() {\n" | ||||
| "    return read_ {\n" | ||||
| "      // TODO: Handle Windows line separators.\n" | ||||
| "      var lineSeparator = __buffered.indexOf(\"\n\")\n" | ||||
| "      if (lineSeparator == -1) return null\n" | ||||
| "\n" | ||||
| "      // Split the line at the separator.\n" | ||||
| "      var line = __buffered[0...lineSeparator]\n" | ||||
| "      __buffered = __buffered[lineSeparator + 1..-1]\n" | ||||
| "      return line\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static read_(handleData) {\n" | ||||
| "    // See if we're already buffered enough to immediately produce a result.\n" | ||||
| "    if (__buffered != null && !__buffered.isEmpty) {\n" | ||||
| "      var result = handleData.call()\n" | ||||
| "      if (result != null) return result\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    if (__isClosed == true) Fiber.abort(\"Stdin was closed.\")\n" | ||||
| "\n" | ||||
| "    // Otherwise, we need to wait for input to come in.\n" | ||||
| "    __handleData = handleData\n" | ||||
| "\n" | ||||
| "    // TODO: Error if other fiber is already waiting.\n" | ||||
| "    readStart_()\n" | ||||
| "\n" | ||||
| "    __waitingFiber = Fiber.current\n" | ||||
| "    var result = Scheduler.runNextScheduled_()\n" | ||||
| "\n" | ||||
| "    readStop_()\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static onData_(data) {\n" | ||||
| "    // If data is null, it means stdin just closed.\n" | ||||
| "    if (data == null) {\n" | ||||
| "      __isClosed = true\n" | ||||
| "      readStop_()\n" | ||||
| "\n" | ||||
| "      if (__buffered != null) {\n" | ||||
| "        // TODO: Is this correct for readByte()?\n" | ||||
| "        // Emit the last remaining bytes.\n" | ||||
| "        var result = __buffered\n" | ||||
| "        __buffered = null\n" | ||||
| "        __waitingFiber.transfer(result)\n" | ||||
| "      } else {\n" | ||||
| "        __waitingFiber.transferError(\"Stdin was closed.\")\n" | ||||
| "      }\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    // Append to the buffer.\n" | ||||
| "    if (__buffered == null) {\n" | ||||
| "      __buffered = data\n" | ||||
| "    } else {\n" | ||||
| "      // TODO: Instead of concatenating strings each time, it's probably faster\n" | ||||
| "      // to keep a list of buffers and flatten lazily.\n" | ||||
| "      __buffered = __buffered + data\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    // Ask the data handler if we have a complete result now.\n" | ||||
| "    var result = __handleData.call()\n" | ||||
| "    if (result != null) __waitingFiber.transfer(result)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  foreign static readStart_()\n" | ||||
| "  foreign static readStop_()\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class Stdout {\n" | ||||
| "  foreign static flush()\n" | ||||
| "}\n"; | ||||
							
								
								
									
										71
									
								
								src/logic/wren/module/os.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/logic/wren/module/os.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| #include "os.h" | ||||
| #include "wren.h" | ||||
| 
 | ||||
| #if __APPLE__ | ||||
|   #include "TargetConditionals.h" | ||||
| #endif | ||||
| 
 | ||||
| int numArgs; | ||||
| const char** args; | ||||
| 
 | ||||
| void osSetArguments(int argc, const char* argv[]) | ||||
| { | ||||
|   numArgs = argc; | ||||
|   args = argv; | ||||
| } | ||||
| 
 | ||||
| void platformName(WrenVM* vm) | ||||
| { | ||||
|   wrenEnsureSlots(vm, 1); | ||||
|    | ||||
|   #ifdef _WIN32 | ||||
|     wrenSetSlotString(vm, 0, "Windows"); | ||||
|   #elif __APPLE__ | ||||
|     #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE | ||||
|       wrenSetSlotString(vm, 0, "iOS"); | ||||
|     #elif TARGET_OS_MAC | ||||
|       wrenSetSlotString(vm, 0, "OS X"); | ||||
|     #else | ||||
|       wrenSetSlotString(vm, 0, "Unknown"); | ||||
|     #endif | ||||
|   #elif __linux__ | ||||
|     wrenSetSlotString(vm, 0, "Linux"); | ||||
|   #elif __unix__ | ||||
|     wrenSetSlotString(vm, 0, "Unix"); | ||||
|   #elif defined(_POSIX_VERSION) | ||||
|     wrenSetSlotString(vm, 0, "POSIX"); | ||||
|   #else | ||||
|     wrenSetSlotString(vm, 0, "Unknown"); | ||||
|   #endif | ||||
| } | ||||
| 
 | ||||
| void platformIsPosix(WrenVM* vm) | ||||
| { | ||||
|   wrenEnsureSlots(vm, 1); | ||||
|    | ||||
|   #ifdef _WIN32 | ||||
|     wrenSetSlotBool(vm, 0, false); | ||||
|   #elif __APPLE__ | ||||
|     wrenSetSlotBool(vm, 0, true); | ||||
|   #elif __linux__ | ||||
|     wrenSetSlotBool(vm, 0, true); | ||||
|   #elif __unix__ | ||||
|     wrenSetSlotBool(vm, 0, true); | ||||
|   #elif defined(_POSIX_VERSION) | ||||
|     wrenSetSlotBool(vm, 0, true); | ||||
|   #else | ||||
|     wrenSetSlotString(vm, 0, false); | ||||
|   #endif | ||||
| } | ||||
| 
 | ||||
| void processAllArguments(WrenVM* vm) | ||||
| { | ||||
|   wrenEnsureSlots(vm, 2); | ||||
|   wrenSetSlotNewList(vm, 0); | ||||
| 
 | ||||
|   for (int i = 0; i < numArgs; i++) | ||||
|   { | ||||
|     wrenSetSlotString(vm, 1, args[i]); | ||||
|     wrenInsertInList(vm, 0, -1, 1); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										9
									
								
								src/logic/wren/module/os.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/logic/wren/module/os.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| #ifndef process_h | ||||
| #define process_h | ||||
| 
 | ||||
| #include "wren.h" | ||||
| 
 | ||||
| // Stores the command line arguments passed to the CLI.
 | ||||
| void osSetArguments(int argc, const char* argv[]); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										13
									
								
								src/logic/wren/module/os.wren
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/logic/wren/module/os.wren
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| class Platform { | ||||
|   foreign static isPosix | ||||
|   foreign static name | ||||
| 
 | ||||
|   static isWindows { name == "Windows" } | ||||
| } | ||||
| 
 | ||||
| class Process { | ||||
|   // TODO: This will need to be smarter when wren supports CLI options. | ||||
|   static arguments { allArguments[2..-1] } | ||||
| 
 | ||||
|   foreign static allArguments | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/logic/wren/module/os.wren.inc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/logic/wren/module/os.wren.inc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| // Generated automatically from src/module/os.wren. Do not edit.
 | ||||
| static const char* osModuleSource = | ||||
| "class Platform {\n" | ||||
| "  foreign static isPosix\n" | ||||
| "  foreign static name\n" | ||||
| "\n" | ||||
| "  static isWindows { name == \"Windows\" }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class Process {\n" | ||||
| "  // TODO: This will need to be smarter when wren supports CLI options.\n" | ||||
| "  static arguments { allArguments[2..-1] }\n" | ||||
| "\n" | ||||
| "  foreign static allArguments\n" | ||||
| "}\n"; | ||||
							
								
								
									
										3
									
								
								src/logic/wren/module/repl.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/logic/wren/module/repl.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| #include "repl.h" | ||||
| #include "wren.h" | ||||
| 
 | ||||
							
								
								
									
										6
									
								
								src/logic/wren/module/repl.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/logic/wren/module/repl.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| #ifndef repl_h | ||||
| #define repl_h | ||||
| 
 | ||||
| #include "wren.h" | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										954
									
								
								src/logic/wren/module/repl.wren
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										954
									
								
								src/logic/wren/module/repl.wren
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,954 @@ | |||
| import "meta" for Meta | ||||
| import "io" for Stdin, Stdout | ||||
| import "os" for Platform | ||||
| 
 | ||||
| /// Abstract base class for the REPL. Manages the input line and history, but | ||||
| /// does not render. | ||||
| class Repl { | ||||
|   construct new() { | ||||
|     _cursor = 0 | ||||
|     _line = "" | ||||
| 
 | ||||
|     _history = [] | ||||
|     _historyIndex = 0 | ||||
|   } | ||||
| 
 | ||||
|   cursor { _cursor } | ||||
|   cursor=(value) { _cursor = value } | ||||
|   line { _line } | ||||
|   line=(value) { _line = value } | ||||
| 
 | ||||
|   run() { | ||||
|     Stdin.isRaw = true | ||||
|     refreshLine(false) | ||||
| 
 | ||||
|     while (true) { | ||||
|       var byte = Stdin.readByte() | ||||
|       if (handleChar(byte)) break | ||||
|       refreshLine(true) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   handleChar(byte) { | ||||
|     if (byte == Chars.ctrlC) { | ||||
|       System.print() | ||||
|       return true | ||||
|     } else if (byte == Chars.ctrlD) { | ||||
|       // If the line is empty, Ctrl_D exits. | ||||
|       if (_line.isEmpty) { | ||||
|         System.print() | ||||
|         return true | ||||
|       } | ||||
| 
 | ||||
|       // Otherwise, it deletes the character after the cursor. | ||||
|       deleteRight() | ||||
|     } else if (byte == Chars.tab) { | ||||
|       var completion = getCompletion() | ||||
|       if (completion != null) { | ||||
|         _line = _line + completion | ||||
|         _cursor = _line.count | ||||
|       } | ||||
|     } else if (byte == Chars.ctrlU) { | ||||
|       // Clear the line. | ||||
|       _line = "" | ||||
|       _cursor = 0 | ||||
|     } else if (byte == Chars.ctrlN) { | ||||
|       nextHistory() | ||||
|     } else if (byte == Chars.ctrlP) { | ||||
|       previousHistory() | ||||
|     } else if (byte == Chars.escape) { | ||||
|       var escapeType = Stdin.readByte() | ||||
|       var value = Stdin.readByte() | ||||
|       if (escapeType == Chars.leftBracket) { | ||||
|         // ESC [ sequence. | ||||
|         handleEscapeBracket(value) | ||||
|       } else { | ||||
|         // TODO: Handle ESC 0 sequences. | ||||
|       } | ||||
|     } else if (byte == Chars.carriageReturn) { | ||||
|       executeInput() | ||||
|     } else if (byte == Chars.delete) { | ||||
|       deleteLeft() | ||||
|     } else if (byte >= Chars.space && byte <= Chars.tilde) { | ||||
|       insertChar(byte) | ||||
|     } else if (byte == Chars.ctrlW) { // Handle Ctrl+w | ||||
|       // Delete trailing spaces | ||||
|       while (_cursor != 0 && _line[_cursor - 1] == " ") { | ||||
|         deleteLeft() | ||||
|       } | ||||
|       // Delete until the next space | ||||
|       while (_cursor != 0 && _line[_cursor - 1] != " ") { | ||||
|         deleteLeft() | ||||
|       } | ||||
|     } else { | ||||
|       // TODO: Other shortcuts? | ||||
|       System.print("Unhandled key-code [dec]: %(byte)") | ||||
|     } | ||||
| 
 | ||||
|     return false | ||||
|   } | ||||
| 
 | ||||
|   /// Inserts the character with [byte] value at the current cursor position. | ||||
|   insertChar(byte) { | ||||
|     var char = String.fromCodePoint(byte) | ||||
|     _line = _line[0..._cursor] + char + _line[_cursor..-1] | ||||
|     _cursor = _cursor + 1 | ||||
|   } | ||||
| 
 | ||||
|   /// Deletes the character before the cursor, if any. | ||||
|   deleteLeft() { | ||||
|     if (_cursor == 0) return | ||||
| 
 | ||||
|     // Delete the character before the cursor. | ||||
|     _line = _line[0...(_cursor - 1)] + _line[_cursor..-1] | ||||
|     _cursor = _cursor - 1 | ||||
|   } | ||||
| 
 | ||||
|   /// Deletes the character after the cursor, if any. | ||||
|   deleteRight() { | ||||
|     if (_cursor == _line.count) return | ||||
| 
 | ||||
|     // Delete the character after the cursor. | ||||
|     _line = _line[0..._cursor] + _line[(_cursor + 1)..-1] | ||||
|   } | ||||
| 
 | ||||
|   handleEscapeBracket(byte) { | ||||
|     if (byte == EscapeBracket.up) { | ||||
|       previousHistory() | ||||
|     } else if (byte == EscapeBracket.down) { | ||||
|       nextHistory() | ||||
|     } else if (byte == EscapeBracket.delete) { | ||||
|       deleteRight() | ||||
|       // Consume extra 126 character generated by delete | ||||
|       Stdin.readByte() | ||||
|     } else if (byte == EscapeBracket.end) { | ||||
|       _cursor = _line.count | ||||
|     } else if (byte == EscapeBracket.home) { | ||||
|       _cursor = 0 | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   previousHistory() { | ||||
|     if (_historyIndex == 0) return | ||||
| 
 | ||||
|     _historyIndex = _historyIndex - 1 | ||||
|     _line = _history[_historyIndex] | ||||
|     _cursor = _line.count | ||||
|   } | ||||
| 
 | ||||
|   nextHistory() { | ||||
|     if (_historyIndex >= _history.count) return | ||||
| 
 | ||||
|     _historyIndex = _historyIndex + 1 | ||||
|     if (_historyIndex < _history.count) { | ||||
|       _line = _history[_historyIndex] | ||||
|       _cursor = _line.count | ||||
|     } else { | ||||
|       _line = "" | ||||
|       _cursor = 0 | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   executeInput() { | ||||
|     // Remove the completion hint. | ||||
|     refreshLine(false) | ||||
| 
 | ||||
|     // Add it to the history (if the line is interesting). | ||||
|     if (_line != "" && (_history.isEmpty || _history[-1] != _line)) { | ||||
|       _history.add(_line) | ||||
|       _historyIndex = _history.count | ||||
|     } | ||||
| 
 | ||||
|     // Reset the current line. | ||||
|     var input = _line | ||||
|     _line = "" | ||||
|     _cursor = 0 | ||||
| 
 | ||||
|     System.print() | ||||
| 
 | ||||
|     // Guess if it looks like a statement or expression. If it looks like an | ||||
|     // expression, we try to print the result. | ||||
|     var token = lexFirst(input) | ||||
| 
 | ||||
|     // No code, so do nothing. | ||||
|     if (token == null) return | ||||
| 
 | ||||
|     var isStatement = | ||||
|         token.type == Token.breakKeyword || | ||||
|         token.type == Token.classKeyword || | ||||
|         token.type == Token.forKeyword || | ||||
|         token.type == Token.foreignKeyword || | ||||
|         token.type == Token.ifKeyword || | ||||
|         token.type == Token.importKeyword || | ||||
|         token.type == Token.returnKeyword || | ||||
|         token.type == Token.varKeyword || | ||||
|         token.type == Token.whileKeyword | ||||
| 
 | ||||
|     var closure | ||||
|     if (isStatement) { | ||||
|       closure = Meta.compile(input) | ||||
|     } else { | ||||
|       closure = Meta.compileExpression(input) | ||||
|     } | ||||
| 
 | ||||
|     // Stop if there was a compile error. | ||||
|     if (closure == null) return | ||||
| 
 | ||||
|     var fiber = Fiber.new(closure) | ||||
| 
 | ||||
|     var result = fiber.try() | ||||
|     if (fiber.error != null) { | ||||
|       // TODO: Include callstack. | ||||
|       showRuntimeError("Runtime error: %(fiber.error)") | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     if (!isStatement) { | ||||
|       showResult(result) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   lex(line, includeWhitespace) { | ||||
|     var lexer = Lexer.new(line) | ||||
|     var tokens = [] | ||||
|     while (true) { | ||||
|       var token = lexer.readToken() | ||||
|       if (token.type == Token.eof) break | ||||
| 
 | ||||
|       if (includeWhitespace || | ||||
|           (token.type != Token.comment && token.type != Token.whitespace)) { | ||||
|         tokens.add(token) | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return tokens | ||||
|   } | ||||
| 
 | ||||
|   lexFirst(line) { | ||||
|     var lexer = Lexer.new(line) | ||||
|     while (true) { | ||||
|       var token = lexer.readToken() | ||||
|       if (token.type == Token.eof) return null | ||||
| 
 | ||||
|       if (token.type != Token.comment && token.type != Token.whitespace) { | ||||
|         return token | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Gets the best possible auto-completion for the current line, or null if | ||||
|   /// there is none. The completion is the remaining string to append to the | ||||
|   /// line, not the entire completed line. | ||||
|   getCompletion() { | ||||
|     if (_line.isEmpty) return null | ||||
| 
 | ||||
|     // Only complete if the cursor is at the end. | ||||
|     if (_cursor != _line.count) return null | ||||
| 
 | ||||
|     for (name in Meta.getModuleVariables("repl")) { | ||||
|       // TODO: Also allow completion if the line ends with an identifier but | ||||
|       // has other stuff before it. | ||||
|       if (name.startsWith(_line)) { | ||||
|         return name[_line.count..-1] | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// A reduced functionality REPL that doesn't use ANSI escape sequences. | ||||
| class SimpleRepl is Repl { | ||||
|   construct new() { | ||||
|     super() | ||||
|     _erase = "" | ||||
|   } | ||||
| 
 | ||||
|   refreshLine(showCompletion) { | ||||
|     // A carriage return just moves the cursor to the beginning of the line. | ||||
|     // We have to erase it manually. Since we can't use ANSI escapes, and we | ||||
|     // don't know how wide the terminal is, erase the longest line we've seen | ||||
|     // so far. | ||||
|     if (line.count > _erase.count) _erase = " " * line.count | ||||
|     System.write("\r  %(_erase)") | ||||
| 
 | ||||
|     // Show the prompt at the beginning of the line. | ||||
|     System.write("\r> ") | ||||
| 
 | ||||
|     // Write the line. | ||||
|     System.write(line) | ||||
|     Stdout.flush() | ||||
|   } | ||||
| 
 | ||||
|   showResult(value) { | ||||
|     // TODO: Syntax color based on type? It might be nice to distinguish | ||||
|     // between string results versus stringified results. Otherwise, the | ||||
|     // user can't tell the difference between `true` and "true". | ||||
|     System.print(value) | ||||
|   } | ||||
| 
 | ||||
|   showRuntimeError(message) { | ||||
|     System.print(message) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class AnsiRepl is Repl { | ||||
|   construct new() { | ||||
|     super() | ||||
|   } | ||||
| 
 | ||||
|   handleChar(byte) { | ||||
|     if (byte == Chars.ctrlA) { | ||||
|       cursor = 0 | ||||
|     } else if (byte == Chars.ctrlB) { | ||||
|       cursorLeft() | ||||
|     } else if (byte == Chars.ctrlE) { | ||||
|       cursor = line.count | ||||
|     } else if (byte == Chars.ctrlF) { | ||||
|       cursorRight() | ||||
|     } else if (byte == Chars.ctrlK) { | ||||
|       // Delete everything after the cursor. | ||||
|       line = line[0...cursor] | ||||
|     } else if (byte == Chars.ctrlL) { | ||||
|       // Clear the screen. | ||||
|       System.write("\x1b[2J") | ||||
|       // Move cursor to top left. | ||||
|       System.write("\x1b[H") | ||||
|     } else { | ||||
|       // TODO: Ctrl-T to swap chars. | ||||
|       // TODO: ESC H and F to move to beginning and end of line. (Both ESC | ||||
|       // [ and ESC 0 sequences?) | ||||
|       // TODO: Ctrl-W delete previous word. | ||||
|       return super.handleChar(byte) | ||||
|     } | ||||
| 
 | ||||
|     return false | ||||
|   } | ||||
| 
 | ||||
|   handleEscapeBracket(byte) { | ||||
|     if (byte == EscapeBracket.left) { | ||||
|       cursorLeft() | ||||
|     } else if (byte == EscapeBracket.right) { | ||||
|       cursorRight() | ||||
|     } | ||||
| 
 | ||||
|     super.handleEscapeBracket(byte) | ||||
|   } | ||||
| 
 | ||||
|   /// Move the cursor left one character. | ||||
|   cursorLeft() { | ||||
|     if (cursor > 0) cursor = cursor - 1 | ||||
|   } | ||||
| 
 | ||||
|   /// Move the cursor right one character. | ||||
|   cursorRight() { | ||||
|     // TODO: Take into account multi-byte characters? | ||||
|     if (cursor < line.count) cursor = cursor + 1 | ||||
|   } | ||||
| 
 | ||||
|   refreshLine(showCompletion) { | ||||
|     // Erase the whole line. | ||||
|     System.write("\x1b[2K") | ||||
| 
 | ||||
|     // Show the prompt at the beginning of the line. | ||||
|     System.write(Color.gray) | ||||
|     System.write("\r> ") | ||||
|     System.write(Color.none) | ||||
| 
 | ||||
|     // Syntax highlight the line. | ||||
|     for (token in lex(line, true)) { | ||||
|       if (token.type == Token.eof) break | ||||
| 
 | ||||
|       System.write(TOKEN_COLORS[token.type]) | ||||
|       System.write(token.text) | ||||
|       System.write(Color.none) | ||||
|     } | ||||
| 
 | ||||
|     if (showCompletion) { | ||||
|       var completion = getCompletion() | ||||
|       if (completion != null) { | ||||
|         System.write("%(Color.gray)%(completion)%(Color.none)") | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // Position the cursor. | ||||
|     System.write("\r\x1b[%(2 + cursor)C") | ||||
|     Stdout.flush() | ||||
|   } | ||||
| 
 | ||||
|   showResult(value) { | ||||
|     // TODO: Syntax color based on type? It might be nice to distinguish | ||||
|     // between string results versus stringified results. Otherwise, the | ||||
|     // user can't tell the difference between `true` and "true". | ||||
|     System.print("%(Color.brightWhite)%(value)%(Color.none)") | ||||
|   } | ||||
| 
 | ||||
|   showRuntimeError(message) { | ||||
|     System.print("%(Color.red)%(message)%(Color.none)") | ||||
|     // TODO: Print entire stack. | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// ANSI color escape sequences. | ||||
| class Color { | ||||
|   static none { "\x1b[0m" } | ||||
|   static black { "\x1b[30m" } | ||||
|   static red { "\x1b[31m" } | ||||
|   static green { "\x1b[32m" } | ||||
|   static yellow { "\x1b[33m" } | ||||
|   static blue { "\x1b[34m" } | ||||
|   static magenta { "\x1b[35m" } | ||||
|   static cyan { "\x1b[36m" } | ||||
|   static white { "\x1b[37m" } | ||||
| 
 | ||||
|   static gray { "\x1b[30;1m" } | ||||
|   static pink { "\x1b[31;1m" } | ||||
|   static brightWhite { "\x1b[37;1m" } | ||||
| } | ||||
| 
 | ||||
| /// Utilities for working with characters. | ||||
| class Chars { | ||||
|   static ctrlA { 0x01 } | ||||
|   static ctrlB { 0x02 } | ||||
|   static ctrlC { 0x03 } | ||||
|   static ctrlD { 0x04 } | ||||
|   static ctrlE { 0x05 } | ||||
|   static ctrlF { 0x06 } | ||||
|   static tab { 0x09 } | ||||
|   static lineFeed { 0x0a } | ||||
|   static ctrlK { 0x0b } | ||||
|   static ctrlL { 0x0c } | ||||
|   static carriageReturn { 0x0d } | ||||
|   static ctrlN { 0x0e } | ||||
|   static ctrlP { 0x10 } | ||||
|   static ctrlU { 0x15 } | ||||
|   static ctrlW { 0x17 } | ||||
|   static escape { 0x1b } | ||||
|   static space { 0x20 } | ||||
|   static bang { 0x21 } | ||||
|   static quote { 0x22 } | ||||
|   static percent { 0x25 } | ||||
|   static amp { 0x26 } | ||||
|   static leftParen { 0x28 } | ||||
|   static rightParen { 0x29 } | ||||
|   static star { 0x2a } | ||||
|   static plus { 0x2b } | ||||
|   static comma { 0x2c } | ||||
|   static minus { 0x2d } | ||||
|   static dot { 0x2e } | ||||
|   static slash { 0x2f } | ||||
| 
 | ||||
|   static zero { 0x30 } | ||||
|   static nine { 0x39 } | ||||
| 
 | ||||
|   static colon { 0x3a } | ||||
|   static less { 0x3c } | ||||
|   static equal { 0x3d } | ||||
|   static greater { 0x3e } | ||||
|   static question { 0x3f } | ||||
| 
 | ||||
|   static upperA { 0x41 } | ||||
|   static upperF { 0x46 } | ||||
|   static upperZ { 0x5a } | ||||
| 
 | ||||
|   static leftBracket { 0x5b } | ||||
|   static backslash { 0x5c } | ||||
|   static rightBracket { 0x5d } | ||||
|   static caret { 0x5e } | ||||
|   static underscore { 0x5f } | ||||
| 
 | ||||
|   static lowerA { 0x61 } | ||||
|   static lowerF { 0x66 } | ||||
|   static lowerX { 0x78 } | ||||
|   static lowerZ { 0x7a } | ||||
| 
 | ||||
|   static leftBrace { 0x7b } | ||||
|   static pipe { 0x7c } | ||||
|   static rightBrace { 0x7d } | ||||
|   static tilde { 0x7e } | ||||
|   static delete { 0x7f } | ||||
| 
 | ||||
|   static isAlpha(c) { | ||||
|     return c >= lowerA && c <= lowerZ || | ||||
|            c >= upperA && c <= upperZ || | ||||
|            c == underscore | ||||
|   } | ||||
| 
 | ||||
|   static isDigit(c) { c >= zero && c <= nine } | ||||
| 
 | ||||
|   static isAlphaNumeric(c) { isAlpha(c) || isDigit(c) } | ||||
| 
 | ||||
|   static isHexDigit(c) { | ||||
|     return c >= zero && c <= nine || | ||||
|            c >= lowerA && c <= lowerF || | ||||
|            c >= upperA && c <= upperF | ||||
|   } | ||||
| 
 | ||||
|   static isLowerAlpha(c) { c >= lowerA && c <= lowerZ } | ||||
| 
 | ||||
|   static isWhitespace(c) { c == space || c == tab || c == carriageReturn } | ||||
| } | ||||
| 
 | ||||
| class EscapeBracket { | ||||
|   static delete { 0x33 } | ||||
|   static up { 0x41 } | ||||
|   static down { 0x42 } | ||||
|   static right { 0x43 } | ||||
|   static left { 0x44 } | ||||
|   static end { 0x46 } | ||||
|   static home { 0x48 } | ||||
| } | ||||
| 
 | ||||
| class Token { | ||||
|   // Punctuators. | ||||
|   static leftParen { "leftParen" } | ||||
|   static rightParen { "rightParen" } | ||||
|   static leftBracket { "leftBracket" } | ||||
|   static rightBracket { "rightBracket" } | ||||
|   static leftBrace { "leftBrace" } | ||||
|   static rightBrace { "rightBrace" } | ||||
|   static colon { "colon" } | ||||
|   static dot { "dot" } | ||||
|   static dotDot { "dotDot" } | ||||
|   static dotDotDot { "dotDotDot" } | ||||
|   static comma { "comma" } | ||||
|   static star { "star" } | ||||
|   static slash { "slash" } | ||||
|   static percent { "percent" } | ||||
|   static plus { "plus" } | ||||
|   static minus { "minus" } | ||||
|   static pipe { "pipe" } | ||||
|   static pipePipe { "pipePipe" } | ||||
|   static caret { "caret" } | ||||
|   static amp { "amp" } | ||||
|   static ampAmp { "ampAmp" } | ||||
|   static question { "question" } | ||||
|   static bang { "bang" } | ||||
|   static tilde { "tilde" } | ||||
|   static equal { "equal" } | ||||
|   static less { "less" } | ||||
|   static lessEqual { "lessEqual" } | ||||
|   static lessLess { "lessLess" } | ||||
|   static greater { "greater" } | ||||
|   static greaterEqual { "greaterEqual" } | ||||
|   static greaterGreater { "greaterGreater" } | ||||
|   static equalEqual { "equalEqual" } | ||||
|   static bangEqual { "bangEqual" } | ||||
| 
 | ||||
|   // Keywords. | ||||
|   static breakKeyword { "break" } | ||||
|   static classKeyword { "class" } | ||||
|   static constructKeyword { "construct" } | ||||
|   static elseKeyword { "else" } | ||||
|   static falseKeyword { "false" } | ||||
|   static forKeyword { "for" } | ||||
|   static foreignKeyword { "foreign" } | ||||
|   static ifKeyword { "if" } | ||||
|   static importKeyword { "import" } | ||||
|   static inKeyword { "in" } | ||||
|   static isKeyword { "is" } | ||||
|   static nullKeyword { "null" } | ||||
|   static returnKeyword { "return" } | ||||
|   static staticKeyword { "static" } | ||||
|   static superKeyword { "super" } | ||||
|   static thisKeyword { "this" } | ||||
|   static trueKeyword { "true" } | ||||
|   static varKeyword { "var" } | ||||
|   static whileKeyword { "while" } | ||||
| 
 | ||||
|   static field { "field" } | ||||
|   static name { "name" } | ||||
|   static number { "number" } | ||||
|   static string { "string" } | ||||
|   static interpolation { "interpolation" } | ||||
|   static comment { "comment" } | ||||
|   static whitespace { "whitespace" } | ||||
|   static line { "line" } | ||||
|   static error { "error" } | ||||
|   static eof { "eof" } | ||||
| 
 | ||||
|   construct new(source, type, start, length) { | ||||
|     _source = source | ||||
|     _type = type | ||||
|     _start = start | ||||
|     _length = length | ||||
|   } | ||||
| 
 | ||||
|   type { _type } | ||||
|   text { _source[_start...(_start + _length)] } | ||||
| 
 | ||||
|   start { _start } | ||||
|   length { _length } | ||||
| 
 | ||||
|   toString { text } | ||||
| } | ||||
| 
 | ||||
| var KEYWORDS = { | ||||
|   "break": Token.breakKeyword, | ||||
|   "class": Token.classKeyword, | ||||
|   "construct": Token.constructKeyword, | ||||
|   "else": Token.elseKeyword, | ||||
|   "false": Token.falseKeyword, | ||||
|   "for": Token.forKeyword, | ||||
|   "foreign": Token.foreignKeyword, | ||||
|   "if": Token.ifKeyword, | ||||
|   "import": Token.importKeyword, | ||||
|   "in": Token.inKeyword, | ||||
|   "is": Token.isKeyword, | ||||
|   "null": Token.nullKeyword, | ||||
|   "return": Token.returnKeyword, | ||||
|   "static": Token.staticKeyword, | ||||
|   "super": Token.superKeyword, | ||||
|   "this": Token.thisKeyword, | ||||
|   "true": Token.trueKeyword, | ||||
|   "var": Token.varKeyword, | ||||
|   "while": Token.whileKeyword | ||||
| } | ||||
| 
 | ||||
| var TOKEN_COLORS = { | ||||
|   Token.leftParen: Color.gray, | ||||
|   Token.rightParen: Color.gray, | ||||
|   Token.leftBracket: Color.gray, | ||||
|   Token.rightBracket: Color.gray, | ||||
|   Token.leftBrace: Color.gray, | ||||
|   Token.rightBrace: Color.gray, | ||||
|   Token.colon: Color.gray, | ||||
|   Token.dot: Color.gray, | ||||
|   Token.dotDot: Color.none, | ||||
|   Token.dotDotDot: Color.none, | ||||
|   Token.comma: Color.gray, | ||||
|   Token.star: Color.none, | ||||
|   Token.slash: Color.none, | ||||
|   Token.percent: Color.none, | ||||
|   Token.plus: Color.none, | ||||
|   Token.minus: Color.none, | ||||
|   Token.pipe: Color.none, | ||||
|   Token.pipePipe: Color.none, | ||||
|   Token.caret: Color.none, | ||||
|   Token.amp: Color.none, | ||||
|   Token.ampAmp: Color.none, | ||||
|   Token.question: Color.none, | ||||
|   Token.bang: Color.none, | ||||
|   Token.tilde: Color.none, | ||||
|   Token.equal: Color.none, | ||||
|   Token.less: Color.none, | ||||
|   Token.lessEqual: Color.none, | ||||
|   Token.lessLess: Color.none, | ||||
|   Token.greater: Color.none, | ||||
|   Token.greaterEqual: Color.none, | ||||
|   Token.greaterGreater: Color.none, | ||||
|   Token.equalEqual: Color.none, | ||||
|   Token.bangEqual: Color.none, | ||||
| 
 | ||||
|   // Keywords. | ||||
|   Token.breakKeyword: Color.cyan, | ||||
|   Token.classKeyword: Color.cyan, | ||||
|   Token.constructKeyword: Color.cyan, | ||||
|   Token.elseKeyword: Color.cyan, | ||||
|   Token.falseKeyword: Color.cyan, | ||||
|   Token.forKeyword: Color.cyan, | ||||
|   Token.foreignKeyword: Color.cyan, | ||||
|   Token.ifKeyword: Color.cyan, | ||||
|   Token.importKeyword: Color.cyan, | ||||
|   Token.inKeyword: Color.cyan, | ||||
|   Token.isKeyword: Color.cyan, | ||||
|   Token.nullKeyword: Color.cyan, | ||||
|   Token.returnKeyword: Color.cyan, | ||||
|   Token.staticKeyword: Color.cyan, | ||||
|   Token.superKeyword: Color.cyan, | ||||
|   Token.thisKeyword: Color.cyan, | ||||
|   Token.trueKeyword: Color.cyan, | ||||
|   Token.varKeyword: Color.cyan, | ||||
|   Token.whileKeyword: Color.cyan, | ||||
| 
 | ||||
|   Token.field: Color.none, | ||||
|   Token.name: Color.none, | ||||
|   Token.number: Color.magenta, | ||||
|   Token.string: Color.yellow, | ||||
|   Token.interpolation: Color.yellow, | ||||
|   Token.comment: Color.gray, | ||||
|   Token.whitespace: Color.none, | ||||
|   Token.line: Color.none, | ||||
|   Token.error: Color.red, | ||||
|   Token.eof: Color.none, | ||||
| } | ||||
| 
 | ||||
| // Data table for tokens that are tokenized using maximal munch. | ||||
| // | ||||
| // The key is the character that starts the token or tokens. After that is a | ||||
| // list of token types and characters. As long as the next character is matched, | ||||
| // the type will update to the type after that character. | ||||
| var PUNCTUATORS = { | ||||
|   Chars.leftParen: [Token.leftParen], | ||||
|   Chars.rightParen: [Token.rightParen], | ||||
|   Chars.leftBracket: [Token.leftBracket], | ||||
|   Chars.rightBracket: [Token.rightBracket], | ||||
|   Chars.leftBrace: [Token.leftBrace], | ||||
|   Chars.rightBrace: [Token.rightBrace], | ||||
|   Chars.colon: [Token.colon], | ||||
|   Chars.comma: [Token.comma], | ||||
|   Chars.star: [Token.star], | ||||
|   Chars.percent: [Token.percent], | ||||
|   Chars.plus: [Token.plus], | ||||
|   Chars.minus: [Token.minus], | ||||
|   Chars.tilde: [Token.tilde], | ||||
|   Chars.caret: [Token.caret], | ||||
|   Chars.question: [Token.question], | ||||
|   Chars.lineFeed: [Token.line], | ||||
| 
 | ||||
|   Chars.pipe: [Token.pipe, Chars.pipe, Token.pipePipe], | ||||
|   Chars.amp: [Token.amp, Chars.amp, Token.ampAmp], | ||||
|   Chars.bang: [Token.bang, Chars.equal, Token.bangEqual], | ||||
|   Chars.equal: [Token.equal, Chars.equal, Token.equalEqual], | ||||
| 
 | ||||
|   Chars.dot: [Token.dot, Chars.dot, Token.dotDot, Chars.dot, Token.dotDotDot] | ||||
| } | ||||
| 
 | ||||
| /// Tokenizes a string of input. This lexer differs from most in that it | ||||
| /// silently ignores errors from incomplete input, like a string literal with | ||||
| /// no closing quote. That's because this is intended to be run on a line of | ||||
| /// input while the user is still typing it. | ||||
| class Lexer { | ||||
|   construct new(source) { | ||||
|     _source = source | ||||
| 
 | ||||
|     // Due to the magic of UTF-8, we can safely treat Wren source as a series | ||||
|     // of bytes, since the only code points that are meaningful to Wren fit in | ||||
|     // ASCII. The only place where non-ASCII code points can occur is inside | ||||
|     // string literals and comments and the lexer safely treats those as opaque | ||||
|     // bytes. | ||||
|     _bytes = source.bytes | ||||
| 
 | ||||
|     _start = 0 | ||||
|     _current = 0 | ||||
| 
 | ||||
|     // The stack of ongoing interpolated strings. Each element in the list is | ||||
|     // a single level of interpolation nesting. The value of the element is the | ||||
|     // number of unbalanced "(" still remaining to be closed. | ||||
|     _interpolations = [] | ||||
|   } | ||||
| 
 | ||||
|   readToken() { | ||||
|     if (_current >= _bytes.count) return makeToken(Token.eof) | ||||
| 
 | ||||
|     _start = _current | ||||
|     var c = _bytes[_current] | ||||
|     advance() | ||||
| 
 | ||||
|     if (!_interpolations.isEmpty) { | ||||
|       if (c == Chars.leftParen) { | ||||
|         _interpolations[-1] = _interpolations[-1] + 1 | ||||
|       } else if (c == Chars.rightParen) { | ||||
|         _interpolations[-1] = _interpolations[-1] - 1 | ||||
| 
 | ||||
|         // The last ")" in an interpolated expression ends the expression and | ||||
|         // resumes the string. | ||||
|         if (_interpolations[-1] == 0) { | ||||
|           // This is the final ")", so the interpolation expression has ended. | ||||
|           // This ")" now begins the next section of the template string. | ||||
|           _interpolations.removeAt(-1) | ||||
|           return readString() | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (PUNCTUATORS.containsKey(c)) { | ||||
|       var punctuator = PUNCTUATORS[c] | ||||
|       var type = punctuator[0] | ||||
|       var i = 1 | ||||
|       while (i < punctuator.count) { | ||||
|         if (!match(punctuator[i])) break | ||||
|         type = punctuator[i + 1] | ||||
|         i = i + 2 | ||||
|       } | ||||
| 
 | ||||
|       return makeToken(type) | ||||
|     } | ||||
| 
 | ||||
|     // Handle "<", "<<", and "<=". | ||||
|     if (c == Chars.less) { | ||||
|       if (match(Chars.less)) return makeToken(Token.lessLess) | ||||
|       if (match(Chars.equal)) return makeToken(Token.lessEqual) | ||||
|       return makeToken(Token.less) | ||||
|     } | ||||
| 
 | ||||
|     // Handle ">", ">>", and ">=". | ||||
|     if (c == Chars.greater) { | ||||
|       if (match(Chars.greater)) return makeToken(Token.greaterGreater) | ||||
|       if (match(Chars.equal)) return makeToken(Token.greaterEqual) | ||||
|       return makeToken(Token.greater) | ||||
|     } | ||||
| 
 | ||||
|     // Handle "/", "//", and "/*". | ||||
|     if (c == Chars.slash) { | ||||
|       if (match(Chars.slash)) return readLineComment() | ||||
|       if (match(Chars.star)) return readBlockComment() | ||||
|       return makeToken(Token.slash) | ||||
|     } | ||||
| 
 | ||||
|     if (c == Chars.underscore) return readField() | ||||
|     if (c == Chars.quote) return readString() | ||||
| 
 | ||||
|     if (c == Chars.zero && peek() == Chars.lowerX) return readHexNumber() | ||||
|     if (Chars.isWhitespace(c)) return readWhitespace() | ||||
|     if (Chars.isDigit(c)) return readNumber() | ||||
|     if (Chars.isAlpha(c)) return readName() | ||||
| 
 | ||||
|     return makeToken(Token.error) | ||||
|   } | ||||
| 
 | ||||
|   // Reads a line comment until the end of the line is reached. | ||||
|   readLineComment() { | ||||
|     // A line comment stops at the newline since newlines are significant. | ||||
|     while (peek() != Chars.lineFeed && !isAtEnd) { | ||||
|       advance() | ||||
|     } | ||||
| 
 | ||||
|     return makeToken(Token.comment) | ||||
|   } | ||||
| 
 | ||||
|   readBlockComment() { | ||||
|     // Block comments can nest. | ||||
|     var nesting = 1 | ||||
|     while (nesting > 0) { | ||||
|       // TODO: Report error. | ||||
|       if (isAtEnd) break | ||||
| 
 | ||||
|       if (peek() == Chars.slash && peek(1) == Chars.star) { | ||||
|         advance() | ||||
|         advance() | ||||
|         nesting = nesting + 1 | ||||
|       } else if (peek() == Chars.star && peek(1) == Chars.slash) { | ||||
|         advance() | ||||
|         advance() | ||||
|         nesting = nesting - 1 | ||||
|         if (nesting == 0) break | ||||
|       } else { | ||||
|         advance() | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return makeToken(Token.comment) | ||||
|   } | ||||
| 
 | ||||
|   // Reads a static or instance field. | ||||
|   readField() { | ||||
|     var type = Token.field | ||||
| 
 | ||||
|     // Read the rest of the name. | ||||
|     while (match {|c| Chars.isAlphaNumeric(c) }) {} | ||||
| 
 | ||||
|     return makeToken(type) | ||||
|   } | ||||
| 
 | ||||
|   // Reads a string literal. | ||||
|   readString() { | ||||
|     var type = Token.string | ||||
| 
 | ||||
|     while (!isAtEnd) { | ||||
|       var c = _bytes[_current] | ||||
|       advance() | ||||
| 
 | ||||
|       if (c == Chars.backslash) { | ||||
|         // TODO: Process specific escapes and validate them. | ||||
|         if (!isAtEnd) advance() | ||||
|       } else if (c == Chars.percent) { | ||||
|         // Consume the '('. | ||||
|         if (!isAtEnd) advance() | ||||
|         // TODO: Handle missing '('. | ||||
|         _interpolations.add(1) | ||||
|         type = Token.interpolation | ||||
|         break | ||||
|       } else if (c == Chars.quote) { | ||||
|         break | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return makeToken(type) | ||||
|   } | ||||
| 
 | ||||
|   // Reads a number literal. | ||||
|   readHexNumber() { | ||||
|     // Skip past the `x`. | ||||
|     advance() | ||||
| 
 | ||||
|     // Read the rest of the number. | ||||
|     while (match {|c| Chars.isHexDigit(c) }) {} | ||||
|     return makeToken(Token.number) | ||||
|   } | ||||
| 
 | ||||
|   // Reads a series of whitespace characters. | ||||
|   readWhitespace() { | ||||
|     // Read the rest of the whitespace. | ||||
|     while (match {|c| Chars.isWhitespace(c) }) {} | ||||
| 
 | ||||
|     return makeToken(Token.whitespace) | ||||
|   } | ||||
| 
 | ||||
|   // Reads a number literal. | ||||
|   readNumber() { | ||||
|     // Read the rest of the number. | ||||
|     while (match {|c| Chars.isDigit(c) }) {} | ||||
| 
 | ||||
|     // TODO: Floating point, scientific. | ||||
|     return makeToken(Token.number) | ||||
|   } | ||||
| 
 | ||||
|   // Reads an identifier or keyword token. | ||||
|   readName() { | ||||
|     // Read the rest of the name. | ||||
|     while (match {|c| Chars.isAlphaNumeric(c) }) {} | ||||
| 
 | ||||
|     var text = _source[_start..._current] | ||||
|     var type = Token.name | ||||
|     if (KEYWORDS.containsKey(text)) { | ||||
|       type = KEYWORDS[text] | ||||
|     } | ||||
| 
 | ||||
|     return Token.new(_source, type, _start, _current - _start) | ||||
|   } | ||||
| 
 | ||||
|   // Returns `true` if we have scanned all characters. | ||||
|   isAtEnd { _current >= _bytes.count } | ||||
| 
 | ||||
|   // Advances past the current character. | ||||
|   advance() { | ||||
|     _current = _current + 1 | ||||
|   } | ||||
| 
 | ||||
|   // Returns the byte value of the current character. | ||||
|   peek() { peek(0) } | ||||
| 
 | ||||
|   // Returns the byte value of the character [n] bytes past the current | ||||
|   // character. | ||||
|   peek(n) { | ||||
|     if (_current + n >= _bytes.count) return -1 | ||||
|     return _bytes[_current + n] | ||||
|   } | ||||
| 
 | ||||
|   // Consumes the current character if it matches [condition], which can be a | ||||
|   // numeric code point value or a function that takes a code point and returns | ||||
|   // `true` if the code point matches. | ||||
|   match(condition) { | ||||
|     if (isAtEnd) return false | ||||
| 
 | ||||
|     var c = _bytes[_current] | ||||
|     if (condition is Fn) { | ||||
|       if (!condition.call(c)) return false | ||||
|     } else if (c != condition) { | ||||
|       return false | ||||
|     } | ||||
| 
 | ||||
|     advance() | ||||
|     return true | ||||
|   } | ||||
| 
 | ||||
|   // Creates a token of [type] from the current character range. | ||||
|   makeToken(type) { Token.new(_source, type, _start, _current - _start) } | ||||
| } | ||||
| 
 | ||||
| // Fire up the REPL. We use ANSI when talking to a POSIX TTY. | ||||
| if (Platform.isPosix && Stdin.isTerminal) { | ||||
|   AnsiRepl.new().run() | ||||
| } else { | ||||
|   // ANSI escape sequences probably aren't supported, so degrade. | ||||
|   SimpleRepl.new().run() | ||||
| } | ||||
							
								
								
									
										956
									
								
								src/logic/wren/module/repl.wren.inc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										956
									
								
								src/logic/wren/module/repl.wren.inc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,956 @@ | |||
| // Generated automatically from src/module/repl.wren. Do not edit.
 | ||||
| static const char* replModuleSource = | ||||
| "import \"meta\" for Meta\n" | ||||
| "import \"io\" for Stdin, Stdout\n" | ||||
| "import \"os\" for Platform\n" | ||||
| "\n" | ||||
| "/// Abstract base class for the REPL. Manages the input line and history, but\n" | ||||
| "/// does not render.\n" | ||||
| "class Repl {\n" | ||||
| "  construct new() {\n" | ||||
| "    _cursor = 0\n" | ||||
| "    _line = \"\"\n" | ||||
| "\n" | ||||
| "    _history = []\n" | ||||
| "    _historyIndex = 0\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  cursor { _cursor }\n" | ||||
| "  cursor=(value) { _cursor = value }\n" | ||||
| "  line { _line }\n" | ||||
| "  line=(value) { _line = value }\n" | ||||
| "\n" | ||||
| "  run() {\n" | ||||
| "    Stdin.isRaw = true\n" | ||||
| "    refreshLine(false)\n" | ||||
| "\n" | ||||
| "    while (true) {\n" | ||||
| "      var byte = Stdin.readByte()\n" | ||||
| "      if (handleChar(byte)) break\n" | ||||
| "      refreshLine(true)\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  handleChar(byte) {\n" | ||||
| "    if (byte == Chars.ctrlC) {\n" | ||||
| "      System.print()\n" | ||||
| "      return true\n" | ||||
| "    } else if (byte == Chars.ctrlD) {\n" | ||||
| "      // If the line is empty, Ctrl_D exits.\n" | ||||
| "      if (_line.isEmpty) {\n" | ||||
| "        System.print()\n" | ||||
| "        return true\n" | ||||
| "      }\n" | ||||
| "\n" | ||||
| "      // Otherwise, it deletes the character after the cursor.\n" | ||||
| "      deleteRight()\n" | ||||
| "    } else if (byte == Chars.tab) {\n" | ||||
| "      var completion = getCompletion()\n" | ||||
| "      if (completion != null) {\n" | ||||
| "        _line = _line + completion\n" | ||||
| "        _cursor = _line.count\n" | ||||
| "      }\n" | ||||
| "    } else if (byte == Chars.ctrlU) {\n" | ||||
| "      // Clear the line.\n" | ||||
| "      _line = \"\"\n" | ||||
| "      _cursor = 0\n" | ||||
| "    } else if (byte == Chars.ctrlN) {\n" | ||||
| "      nextHistory()\n" | ||||
| "    } else if (byte == Chars.ctrlP) {\n" | ||||
| "      previousHistory()\n" | ||||
| "    } else if (byte == Chars.escape) {\n" | ||||
| "      var escapeType = Stdin.readByte()\n" | ||||
| "      var value = Stdin.readByte()\n" | ||||
| "      if (escapeType == Chars.leftBracket) {\n" | ||||
| "        // ESC [ sequence.\n" | ||||
| "        handleEscapeBracket(value)\n" | ||||
| "      } else {\n" | ||||
| "        // TODO: Handle ESC 0 sequences.\n" | ||||
| "      }\n" | ||||
| "    } else if (byte == Chars.carriageReturn) {\n" | ||||
| "      executeInput()\n" | ||||
| "    } else if (byte == Chars.delete) {\n" | ||||
| "      deleteLeft()\n" | ||||
| "    } else if (byte >= Chars.space && byte <= Chars.tilde) {\n" | ||||
| "      insertChar(byte)\n" | ||||
| "    } else if (byte == Chars.ctrlW) { // Handle Ctrl+w\n" | ||||
| "      // Delete trailing spaces\n" | ||||
| "      while (_cursor != 0 && _line[_cursor - 1] == \" \") {\n" | ||||
| "        deleteLeft()\n" | ||||
| "      }\n" | ||||
| "      // Delete until the next space\n" | ||||
| "      while (_cursor != 0 && _line[_cursor - 1] != \" \") {\n" | ||||
| "        deleteLeft()\n" | ||||
| "      }\n" | ||||
| "    } else {\n" | ||||
| "      // TODO: Other shortcuts?\n" | ||||
| "      System.print(\"Unhandled key-code [dec]: %(byte)\")\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return false\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  /// Inserts the character with [byte] value at the current cursor position.\n" | ||||
| "  insertChar(byte) {\n" | ||||
| "    var char = String.fromCodePoint(byte)\n" | ||||
| "    _line = _line[0..._cursor] + char + _line[_cursor..-1]\n" | ||||
| "    _cursor = _cursor + 1\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  /// Deletes the character before the cursor, if any.\n" | ||||
| "  deleteLeft() {\n" | ||||
| "    if (_cursor == 0) return\n" | ||||
| "\n" | ||||
| "    // Delete the character before the cursor.\n" | ||||
| "    _line = _line[0...(_cursor - 1)] + _line[_cursor..-1]\n" | ||||
| "    _cursor = _cursor - 1\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  /// Deletes the character after the cursor, if any.\n" | ||||
| "  deleteRight() {\n" | ||||
| "    if (_cursor == _line.count) return\n" | ||||
| "\n" | ||||
| "    // Delete the character after the cursor.\n" | ||||
| "    _line = _line[0..._cursor] + _line[(_cursor + 1)..-1]\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  handleEscapeBracket(byte) {\n" | ||||
| "    if (byte == EscapeBracket.up) {\n" | ||||
| "      previousHistory()\n" | ||||
| "    } else if (byte == EscapeBracket.down) {\n" | ||||
| "      nextHistory()\n" | ||||
| "    } else if (byte == EscapeBracket.delete) {\n" | ||||
| "      deleteRight()\n" | ||||
| "      // Consume extra 126 character generated by delete\n" | ||||
| "      Stdin.readByte()\n" | ||||
| "    } else if (byte == EscapeBracket.end) {\n" | ||||
| "      _cursor = _line.count\n" | ||||
| "    } else if (byte == EscapeBracket.home) {\n" | ||||
| "      _cursor = 0\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  previousHistory() {\n" | ||||
| "    if (_historyIndex == 0) return\n" | ||||
| "\n" | ||||
| "    _historyIndex = _historyIndex - 1\n" | ||||
| "    _line = _history[_historyIndex]\n" | ||||
| "    _cursor = _line.count\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  nextHistory() {\n" | ||||
| "    if (_historyIndex >= _history.count) return\n" | ||||
| "\n" | ||||
| "    _historyIndex = _historyIndex + 1\n" | ||||
| "    if (_historyIndex < _history.count) {\n" | ||||
| "      _line = _history[_historyIndex]\n" | ||||
| "      _cursor = _line.count\n" | ||||
| "    } else {\n" | ||||
| "      _line = \"\"\n" | ||||
| "      _cursor = 0\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  executeInput() {\n" | ||||
| "    // Remove the completion hint.\n" | ||||
| "    refreshLine(false)\n" | ||||
| "\n" | ||||
| "    // Add it to the history (if the line is interesting).\n" | ||||
| "    if (_line != \"\" && (_history.isEmpty || _history[-1] != _line)) {\n" | ||||
| "      _history.add(_line)\n" | ||||
| "      _historyIndex = _history.count\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    // Reset the current line.\n" | ||||
| "    var input = _line\n" | ||||
| "    _line = \"\"\n" | ||||
| "    _cursor = 0\n" | ||||
| "\n" | ||||
| "    System.print()\n" | ||||
| "\n" | ||||
| "    // Guess if it looks like a statement or expression. If it looks like an\n" | ||||
| "    // expression, we try to print the result.\n" | ||||
| "    var token = lexFirst(input)\n" | ||||
| "\n" | ||||
| "    // No code, so do nothing.\n" | ||||
| "    if (token == null) return\n" | ||||
| "\n" | ||||
| "    var isStatement =\n" | ||||
| "        token.type == Token.breakKeyword ||\n" | ||||
| "        token.type == Token.classKeyword ||\n" | ||||
| "        token.type == Token.forKeyword ||\n" | ||||
| "        token.type == Token.foreignKeyword ||\n" | ||||
| "        token.type == Token.ifKeyword ||\n" | ||||
| "        token.type == Token.importKeyword ||\n" | ||||
| "        token.type == Token.returnKeyword ||\n" | ||||
| "        token.type == Token.varKeyword ||\n" | ||||
| "        token.type == Token.whileKeyword\n" | ||||
| "\n" | ||||
| "    var closure\n" | ||||
| "    if (isStatement) {\n" | ||||
| "      closure = Meta.compile(input)\n" | ||||
| "    } else {\n" | ||||
| "      closure = Meta.compileExpression(input)\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    // Stop if there was a compile error.\n" | ||||
| "    if (closure == null) return\n" | ||||
| "\n" | ||||
| "    var fiber = Fiber.new(closure)\n" | ||||
| "\n" | ||||
| "    var result = fiber.try()\n" | ||||
| "    if (fiber.error != null) {\n" | ||||
| "      // TODO: Include callstack.\n" | ||||
| "      showRuntimeError(\"Runtime error: %(fiber.error)\")\n" | ||||
| "      return\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    if (!isStatement) {\n" | ||||
| "      showResult(result)\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  lex(line, includeWhitespace) {\n" | ||||
| "    var lexer = Lexer.new(line)\n" | ||||
| "    var tokens = []\n" | ||||
| "    while (true) {\n" | ||||
| "      var token = lexer.readToken()\n" | ||||
| "      if (token.type == Token.eof) break\n" | ||||
| "\n" | ||||
| "      if (includeWhitespace ||\n" | ||||
| "          (token.type != Token.comment && token.type != Token.whitespace)) {\n" | ||||
| "        tokens.add(token)\n" | ||||
| "      }\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return tokens\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  lexFirst(line) {\n" | ||||
| "    var lexer = Lexer.new(line)\n" | ||||
| "    while (true) {\n" | ||||
| "      var token = lexer.readToken()\n" | ||||
| "      if (token.type == Token.eof) return null\n" | ||||
| "\n" | ||||
| "      if (token.type != Token.comment && token.type != Token.whitespace) {\n" | ||||
| "        return token\n" | ||||
| "      }\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  /// Gets the best possible auto-completion for the current line, or null if\n" | ||||
| "  /// there is none. The completion is the remaining string to append to the\n" | ||||
| "  /// line, not the entire completed line.\n" | ||||
| "  getCompletion() {\n" | ||||
| "    if (_line.isEmpty) return null\n" | ||||
| "\n" | ||||
| "    // Only complete if the cursor is at the end.\n" | ||||
| "    if (_cursor != _line.count) return null\n" | ||||
| "\n" | ||||
| "    for (name in Meta.getModuleVariables(\"repl\")) {\n" | ||||
| "      // TODO: Also allow completion if the line ends with an identifier but\n" | ||||
| "      // has other stuff before it.\n" | ||||
| "      if (name.startsWith(_line)) {\n" | ||||
| "        return name[_line.count..-1]\n" | ||||
| "      }\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "/// A reduced functionality REPL that doesn't use ANSI escape sequences.\n" | ||||
| "class SimpleRepl is Repl {\n" | ||||
| "  construct new() {\n" | ||||
| "    super()\n" | ||||
| "    _erase = \"\"\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  refreshLine(showCompletion) {\n" | ||||
| "    // A carriage return just moves the cursor to the beginning of the line.\n" | ||||
| "    // We have to erase it manually. Since we can't use ANSI escapes, and we\n" | ||||
| "    // don't know how wide the terminal is, erase the longest line we've seen\n" | ||||
| "    // so far.\n" | ||||
| "    if (line.count > _erase.count) _erase = \" \" * line.count\n" | ||||
| "    System.write(\"\r  %(_erase)\")\n" | ||||
| "\n" | ||||
| "    // Show the prompt at the beginning of the line.\n" | ||||
| "    System.write(\"\r> \")\n" | ||||
| "\n" | ||||
| "    // Write the line.\n" | ||||
| "    System.write(line)\n" | ||||
| "    Stdout.flush()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  showResult(value) {\n" | ||||
| "    // TODO: Syntax color based on type? It might be nice to distinguish\n" | ||||
| "    // between string results versus stringified results. Otherwise, the\n" | ||||
| "    // user can't tell the difference between `true` and \"true\".\n" | ||||
| "    System.print(value)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  showRuntimeError(message) {\n" | ||||
| "    System.print(message)\n" | ||||
| "  }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class AnsiRepl is Repl {\n" | ||||
| "  construct new() {\n" | ||||
| "    super()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  handleChar(byte) {\n" | ||||
| "    if (byte == Chars.ctrlA) {\n" | ||||
| "      cursor = 0\n" | ||||
| "    } else if (byte == Chars.ctrlB) {\n" | ||||
| "      cursorLeft()\n" | ||||
| "    } else if (byte == Chars.ctrlE) {\n" | ||||
| "      cursor = line.count\n" | ||||
| "    } else if (byte == Chars.ctrlF) {\n" | ||||
| "      cursorRight()\n" | ||||
| "    } else if (byte == Chars.ctrlK) {\n" | ||||
| "      // Delete everything after the cursor.\n" | ||||
| "      line = line[0...cursor]\n" | ||||
| "    } else if (byte == Chars.ctrlL) {\n" | ||||
| "      // Clear the screen.\n" | ||||
| "      System.write(\"\x1b[2J\")\n" | ||||
| "      // Move cursor to top left.\n" | ||||
| "      System.write(\"\x1b[H\")\n" | ||||
| "    } else {\n" | ||||
| "      // TODO: Ctrl-T to swap chars.\n" | ||||
| "      // TODO: ESC H and F to move to beginning and end of line. (Both ESC\n" | ||||
| "      // [ and ESC 0 sequences?)\n" | ||||
| "      // TODO: Ctrl-W delete previous word.\n" | ||||
| "      return super.handleChar(byte)\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return false\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  handleEscapeBracket(byte) {\n" | ||||
| "    if (byte == EscapeBracket.left) {\n" | ||||
| "      cursorLeft()\n" | ||||
| "    } else if (byte == EscapeBracket.right) {\n" | ||||
| "      cursorRight()\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    super.handleEscapeBracket(byte)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  /// Move the cursor left one character.\n" | ||||
| "  cursorLeft() {\n" | ||||
| "    if (cursor > 0) cursor = cursor - 1\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  /// Move the cursor right one character.\n" | ||||
| "  cursorRight() {\n" | ||||
| "    // TODO: Take into account multi-byte characters?\n" | ||||
| "    if (cursor < line.count) cursor = cursor + 1\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  refreshLine(showCompletion) {\n" | ||||
| "    // Erase the whole line.\n" | ||||
| "    System.write(\"\x1b[2K\")\n" | ||||
| "\n" | ||||
| "    // Show the prompt at the beginning of the line.\n" | ||||
| "    System.write(Color.gray)\n" | ||||
| "    System.write(\"\r> \")\n" | ||||
| "    System.write(Color.none)\n" | ||||
| "\n" | ||||
| "    // Syntax highlight the line.\n" | ||||
| "    for (token in lex(line, true)) {\n" | ||||
| "      if (token.type == Token.eof) break\n" | ||||
| "\n" | ||||
| "      System.write(TOKEN_COLORS[token.type])\n" | ||||
| "      System.write(token.text)\n" | ||||
| "      System.write(Color.none)\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    if (showCompletion) {\n" | ||||
| "      var completion = getCompletion()\n" | ||||
| "      if (completion != null) {\n" | ||||
| "        System.write(\"%(Color.gray)%(completion)%(Color.none)\")\n" | ||||
| "      }\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    // Position the cursor.\n" | ||||
| "    System.write(\"\r\x1b[%(2 + cursor)C\")\n" | ||||
| "    Stdout.flush()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  showResult(value) {\n" | ||||
| "    // TODO: Syntax color based on type? It might be nice to distinguish\n" | ||||
| "    // between string results versus stringified results. Otherwise, the\n" | ||||
| "    // user can't tell the difference between `true` and \"true\".\n" | ||||
| "    System.print(\"%(Color.brightWhite)%(value)%(Color.none)\")\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  showRuntimeError(message) {\n" | ||||
| "    System.print(\"%(Color.red)%(message)%(Color.none)\")\n" | ||||
| "    // TODO: Print entire stack.\n" | ||||
| "  }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "/// ANSI color escape sequences.\n" | ||||
| "class Color {\n" | ||||
| "  static none { \"\x1b[0m\" }\n" | ||||
| "  static black { \"\x1b[30m\" }\n" | ||||
| "  static red { \"\x1b[31m\" }\n" | ||||
| "  static green { \"\x1b[32m\" }\n" | ||||
| "  static yellow { \"\x1b[33m\" }\n" | ||||
| "  static blue { \"\x1b[34m\" }\n" | ||||
| "  static magenta { \"\x1b[35m\" }\n" | ||||
| "  static cyan { \"\x1b[36m\" }\n" | ||||
| "  static white { \"\x1b[37m\" }\n" | ||||
| "\n" | ||||
| "  static gray { \"\x1b[30;1m\" }\n" | ||||
| "  static pink { \"\x1b[31;1m\" }\n" | ||||
| "  static brightWhite { \"\x1b[37;1m\" }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "/// Utilities for working with characters.\n" | ||||
| "class Chars {\n" | ||||
| "  static ctrlA { 0x01 }\n" | ||||
| "  static ctrlB { 0x02 }\n" | ||||
| "  static ctrlC { 0x03 }\n" | ||||
| "  static ctrlD { 0x04 }\n" | ||||
| "  static ctrlE { 0x05 }\n" | ||||
| "  static ctrlF { 0x06 }\n" | ||||
| "  static tab { 0x09 }\n" | ||||
| "  static lineFeed { 0x0a }\n" | ||||
| "  static ctrlK { 0x0b }\n" | ||||
| "  static ctrlL { 0x0c }\n" | ||||
| "  static carriageReturn { 0x0d }\n" | ||||
| "  static ctrlN { 0x0e }\n" | ||||
| "  static ctrlP { 0x10 }\n" | ||||
| "  static ctrlU { 0x15 }\n" | ||||
| "  static ctrlW { 0x17 }\n" | ||||
| "  static escape { 0x1b }\n" | ||||
| "  static space { 0x20 }\n" | ||||
| "  static bang { 0x21 }\n" | ||||
| "  static quote { 0x22 }\n" | ||||
| "  static percent { 0x25 }\n" | ||||
| "  static amp { 0x26 }\n" | ||||
| "  static leftParen { 0x28 }\n" | ||||
| "  static rightParen { 0x29 }\n" | ||||
| "  static star { 0x2a }\n" | ||||
| "  static plus { 0x2b }\n" | ||||
| "  static comma { 0x2c }\n" | ||||
| "  static minus { 0x2d }\n" | ||||
| "  static dot { 0x2e }\n" | ||||
| "  static slash { 0x2f }\n" | ||||
| "\n" | ||||
| "  static zero { 0x30 }\n" | ||||
| "  static nine { 0x39 }\n" | ||||
| "\n" | ||||
| "  static colon { 0x3a }\n" | ||||
| "  static less { 0x3c }\n" | ||||
| "  static equal { 0x3d }\n" | ||||
| "  static greater { 0x3e }\n" | ||||
| "  static question { 0x3f }\n" | ||||
| "\n" | ||||
| "  static upperA { 0x41 }\n" | ||||
| "  static upperF { 0x46 }\n" | ||||
| "  static upperZ { 0x5a }\n" | ||||
| "\n" | ||||
| "  static leftBracket { 0x5b }\n" | ||||
| "  static backslash { 0x5c }\n" | ||||
| "  static rightBracket { 0x5d }\n" | ||||
| "  static caret { 0x5e }\n" | ||||
| "  static underscore { 0x5f }\n" | ||||
| "\n" | ||||
| "  static lowerA { 0x61 }\n" | ||||
| "  static lowerF { 0x66 }\n" | ||||
| "  static lowerX { 0x78 }\n" | ||||
| "  static lowerZ { 0x7a }\n" | ||||
| "\n" | ||||
| "  static leftBrace { 0x7b }\n" | ||||
| "  static pipe { 0x7c }\n" | ||||
| "  static rightBrace { 0x7d }\n" | ||||
| "  static tilde { 0x7e }\n" | ||||
| "  static delete { 0x7f }\n" | ||||
| "\n" | ||||
| "  static isAlpha(c) {\n" | ||||
| "    return c >= lowerA && c <= lowerZ ||\n" | ||||
| "           c >= upperA && c <= upperZ ||\n" | ||||
| "           c == underscore\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static isDigit(c) { c >= zero && c <= nine }\n" | ||||
| "\n" | ||||
| "  static isAlphaNumeric(c) { isAlpha(c) || isDigit(c) }\n" | ||||
| "\n" | ||||
| "  static isHexDigit(c) {\n" | ||||
| "    return c >= zero && c <= nine ||\n" | ||||
| "           c >= lowerA && c <= lowerF ||\n" | ||||
| "           c >= upperA && c <= upperF\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static isLowerAlpha(c) { c >= lowerA && c <= lowerZ }\n" | ||||
| "\n" | ||||
| "  static isWhitespace(c) { c == space || c == tab || c == carriageReturn }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class EscapeBracket {\n" | ||||
| "  static delete { 0x33 }\n" | ||||
| "  static up { 0x41 }\n" | ||||
| "  static down { 0x42 }\n" | ||||
| "  static right { 0x43 }\n" | ||||
| "  static left { 0x44 }\n" | ||||
| "  static end { 0x46 }\n" | ||||
| "  static home { 0x48 }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class Token {\n" | ||||
| "  // Punctuators.\n" | ||||
| "  static leftParen { \"leftParen\" }\n" | ||||
| "  static rightParen { \"rightParen\" }\n" | ||||
| "  static leftBracket { \"leftBracket\" }\n" | ||||
| "  static rightBracket { \"rightBracket\" }\n" | ||||
| "  static leftBrace { \"leftBrace\" }\n" | ||||
| "  static rightBrace { \"rightBrace\" }\n" | ||||
| "  static colon { \"colon\" }\n" | ||||
| "  static dot { \"dot\" }\n" | ||||
| "  static dotDot { \"dotDot\" }\n" | ||||
| "  static dotDotDot { \"dotDotDot\" }\n" | ||||
| "  static comma { \"comma\" }\n" | ||||
| "  static star { \"star\" }\n" | ||||
| "  static slash { \"slash\" }\n" | ||||
| "  static percent { \"percent\" }\n" | ||||
| "  static plus { \"plus\" }\n" | ||||
| "  static minus { \"minus\" }\n" | ||||
| "  static pipe { \"pipe\" }\n" | ||||
| "  static pipePipe { \"pipePipe\" }\n" | ||||
| "  static caret { \"caret\" }\n" | ||||
| "  static amp { \"amp\" }\n" | ||||
| "  static ampAmp { \"ampAmp\" }\n" | ||||
| "  static question { \"question\" }\n" | ||||
| "  static bang { \"bang\" }\n" | ||||
| "  static tilde { \"tilde\" }\n" | ||||
| "  static equal { \"equal\" }\n" | ||||
| "  static less { \"less\" }\n" | ||||
| "  static lessEqual { \"lessEqual\" }\n" | ||||
| "  static lessLess { \"lessLess\" }\n" | ||||
| "  static greater { \"greater\" }\n" | ||||
| "  static greaterEqual { \"greaterEqual\" }\n" | ||||
| "  static greaterGreater { \"greaterGreater\" }\n" | ||||
| "  static equalEqual { \"equalEqual\" }\n" | ||||
| "  static bangEqual { \"bangEqual\" }\n" | ||||
| "\n" | ||||
| "  // Keywords.\n" | ||||
| "  static breakKeyword { \"break\" }\n" | ||||
| "  static classKeyword { \"class\" }\n" | ||||
| "  static constructKeyword { \"construct\" }\n" | ||||
| "  static elseKeyword { \"else\" }\n" | ||||
| "  static falseKeyword { \"false\" }\n" | ||||
| "  static forKeyword { \"for\" }\n" | ||||
| "  static foreignKeyword { \"foreign\" }\n" | ||||
| "  static ifKeyword { \"if\" }\n" | ||||
| "  static importKeyword { \"import\" }\n" | ||||
| "  static inKeyword { \"in\" }\n" | ||||
| "  static isKeyword { \"is\" }\n" | ||||
| "  static nullKeyword { \"null\" }\n" | ||||
| "  static returnKeyword { \"return\" }\n" | ||||
| "  static staticKeyword { \"static\" }\n" | ||||
| "  static superKeyword { \"super\" }\n" | ||||
| "  static thisKeyword { \"this\" }\n" | ||||
| "  static trueKeyword { \"true\" }\n" | ||||
| "  static varKeyword { \"var\" }\n" | ||||
| "  static whileKeyword { \"while\" }\n" | ||||
| "\n" | ||||
| "  static field { \"field\" }\n" | ||||
| "  static name { \"name\" }\n" | ||||
| "  static number { \"number\" }\n" | ||||
| "  static string { \"string\" }\n" | ||||
| "  static interpolation { \"interpolation\" }\n" | ||||
| "  static comment { \"comment\" }\n" | ||||
| "  static whitespace { \"whitespace\" }\n" | ||||
| "  static line { \"line\" }\n" | ||||
| "  static error { \"error\" }\n" | ||||
| "  static eof { \"eof\" }\n" | ||||
| "\n" | ||||
| "  construct new(source, type, start, length) {\n" | ||||
| "    _source = source\n" | ||||
| "    _type = type\n" | ||||
| "    _start = start\n" | ||||
| "    _length = length\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  type { _type }\n" | ||||
| "  text { _source[_start...(_start + _length)] }\n" | ||||
| "\n" | ||||
| "  start { _start }\n" | ||||
| "  length { _length }\n" | ||||
| "\n" | ||||
| "  toString { text }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "var KEYWORDS = {\n" | ||||
| "  \"break\": Token.breakKeyword,\n" | ||||
| "  \"class\": Token.classKeyword,\n" | ||||
| "  \"construct\": Token.constructKeyword,\n" | ||||
| "  \"else\": Token.elseKeyword,\n" | ||||
| "  \"false\": Token.falseKeyword,\n" | ||||
| "  \"for\": Token.forKeyword,\n" | ||||
| "  \"foreign\": Token.foreignKeyword,\n" | ||||
| "  \"if\": Token.ifKeyword,\n" | ||||
| "  \"import\": Token.importKeyword,\n" | ||||
| "  \"in\": Token.inKeyword,\n" | ||||
| "  \"is\": Token.isKeyword,\n" | ||||
| "  \"null\": Token.nullKeyword,\n" | ||||
| "  \"return\": Token.returnKeyword,\n" | ||||
| "  \"static\": Token.staticKeyword,\n" | ||||
| "  \"super\": Token.superKeyword,\n" | ||||
| "  \"this\": Token.thisKeyword,\n" | ||||
| "  \"true\": Token.trueKeyword,\n" | ||||
| "  \"var\": Token.varKeyword,\n" | ||||
| "  \"while\": Token.whileKeyword\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "var TOKEN_COLORS = {\n" | ||||
| "  Token.leftParen: Color.gray,\n" | ||||
| "  Token.rightParen: Color.gray,\n" | ||||
| "  Token.leftBracket: Color.gray,\n" | ||||
| "  Token.rightBracket: Color.gray,\n" | ||||
| "  Token.leftBrace: Color.gray,\n" | ||||
| "  Token.rightBrace: Color.gray,\n" | ||||
| "  Token.colon: Color.gray,\n" | ||||
| "  Token.dot: Color.gray,\n" | ||||
| "  Token.dotDot: Color.none,\n" | ||||
| "  Token.dotDotDot: Color.none,\n" | ||||
| "  Token.comma: Color.gray,\n" | ||||
| "  Token.star: Color.none,\n" | ||||
| "  Token.slash: Color.none,\n" | ||||
| "  Token.percent: Color.none,\n" | ||||
| "  Token.plus: Color.none,\n" | ||||
| "  Token.minus: Color.none,\n" | ||||
| "  Token.pipe: Color.none,\n" | ||||
| "  Token.pipePipe: Color.none,\n" | ||||
| "  Token.caret: Color.none,\n" | ||||
| "  Token.amp: Color.none,\n" | ||||
| "  Token.ampAmp: Color.none,\n" | ||||
| "  Token.question: Color.none,\n" | ||||
| "  Token.bang: Color.none,\n" | ||||
| "  Token.tilde: Color.none,\n" | ||||
| "  Token.equal: Color.none,\n" | ||||
| "  Token.less: Color.none,\n" | ||||
| "  Token.lessEqual: Color.none,\n" | ||||
| "  Token.lessLess: Color.none,\n" | ||||
| "  Token.greater: Color.none,\n" | ||||
| "  Token.greaterEqual: Color.none,\n" | ||||
| "  Token.greaterGreater: Color.none,\n" | ||||
| "  Token.equalEqual: Color.none,\n" | ||||
| "  Token.bangEqual: Color.none,\n" | ||||
| "\n" | ||||
| "  // Keywords.\n" | ||||
| "  Token.breakKeyword: Color.cyan,\n" | ||||
| "  Token.classKeyword: Color.cyan,\n" | ||||
| "  Token.constructKeyword: Color.cyan,\n" | ||||
| "  Token.elseKeyword: Color.cyan,\n" | ||||
| "  Token.falseKeyword: Color.cyan,\n" | ||||
| "  Token.forKeyword: Color.cyan,\n" | ||||
| "  Token.foreignKeyword: Color.cyan,\n" | ||||
| "  Token.ifKeyword: Color.cyan,\n" | ||||
| "  Token.importKeyword: Color.cyan,\n" | ||||
| "  Token.inKeyword: Color.cyan,\n" | ||||
| "  Token.isKeyword: Color.cyan,\n" | ||||
| "  Token.nullKeyword: Color.cyan,\n" | ||||
| "  Token.returnKeyword: Color.cyan,\n" | ||||
| "  Token.staticKeyword: Color.cyan,\n" | ||||
| "  Token.superKeyword: Color.cyan,\n" | ||||
| "  Token.thisKeyword: Color.cyan,\n" | ||||
| "  Token.trueKeyword: Color.cyan,\n" | ||||
| "  Token.varKeyword: Color.cyan,\n" | ||||
| "  Token.whileKeyword: Color.cyan,\n" | ||||
| "\n" | ||||
| "  Token.field: Color.none,\n" | ||||
| "  Token.name: Color.none,\n" | ||||
| "  Token.number: Color.magenta,\n" | ||||
| "  Token.string: Color.yellow,\n" | ||||
| "  Token.interpolation: Color.yellow,\n" | ||||
| "  Token.comment: Color.gray,\n" | ||||
| "  Token.whitespace: Color.none,\n" | ||||
| "  Token.line: Color.none,\n" | ||||
| "  Token.error: Color.red,\n" | ||||
| "  Token.eof: Color.none,\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "// Data table for tokens that are tokenized using maximal munch.\n" | ||||
| "//\n" | ||||
| "// The key is the character that starts the token or tokens. After that is a\n" | ||||
| "// list of token types and characters. As long as the next character is matched,\n" | ||||
| "// the type will update to the type after that character.\n" | ||||
| "var PUNCTUATORS = {\n" | ||||
| "  Chars.leftParen: [Token.leftParen],\n" | ||||
| "  Chars.rightParen: [Token.rightParen],\n" | ||||
| "  Chars.leftBracket: [Token.leftBracket],\n" | ||||
| "  Chars.rightBracket: [Token.rightBracket],\n" | ||||
| "  Chars.leftBrace: [Token.leftBrace],\n" | ||||
| "  Chars.rightBrace: [Token.rightBrace],\n" | ||||
| "  Chars.colon: [Token.colon],\n" | ||||
| "  Chars.comma: [Token.comma],\n" | ||||
| "  Chars.star: [Token.star],\n" | ||||
| "  Chars.percent: [Token.percent],\n" | ||||
| "  Chars.plus: [Token.plus],\n" | ||||
| "  Chars.minus: [Token.minus],\n" | ||||
| "  Chars.tilde: [Token.tilde],\n" | ||||
| "  Chars.caret: [Token.caret],\n" | ||||
| "  Chars.question: [Token.question],\n" | ||||
| "  Chars.lineFeed: [Token.line],\n" | ||||
| "\n" | ||||
| "  Chars.pipe: [Token.pipe, Chars.pipe, Token.pipePipe],\n" | ||||
| "  Chars.amp: [Token.amp, Chars.amp, Token.ampAmp],\n" | ||||
| "  Chars.bang: [Token.bang, Chars.equal, Token.bangEqual],\n" | ||||
| "  Chars.equal: [Token.equal, Chars.equal, Token.equalEqual],\n" | ||||
| "\n" | ||||
| "  Chars.dot: [Token.dot, Chars.dot, Token.dotDot, Chars.dot, Token.dotDotDot]\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "/// Tokenizes a string of input. This lexer differs from most in that it\n" | ||||
| "/// silently ignores errors from incomplete input, like a string literal with\n" | ||||
| "/// no closing quote. That's because this is intended to be run on a line of\n" | ||||
| "/// input while the user is still typing it.\n" | ||||
| "class Lexer {\n" | ||||
| "  construct new(source) {\n" | ||||
| "    _source = source\n" | ||||
| "\n" | ||||
| "    // Due to the magic of UTF-8, we can safely treat Wren source as a series\n" | ||||
| "    // of bytes, since the only code points that are meaningful to Wren fit in\n" | ||||
| "    // ASCII. The only place where non-ASCII code points can occur is inside\n" | ||||
| "    // string literals and comments and the lexer safely treats those as opaque\n" | ||||
| "    // bytes.\n" | ||||
| "    _bytes = source.bytes\n" | ||||
| "\n" | ||||
| "    _start = 0\n" | ||||
| "    _current = 0\n" | ||||
| "\n" | ||||
| "    // The stack of ongoing interpolated strings. Each element in the list is\n" | ||||
| "    // a single level of interpolation nesting. The value of the element is the\n" | ||||
| "    // number of unbalanced \"(\" still remaining to be closed.\n" | ||||
| "    _interpolations = []\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  readToken() {\n" | ||||
| "    if (_current >= _bytes.count) return makeToken(Token.eof)\n" | ||||
| "\n" | ||||
| "    _start = _current\n" | ||||
| "    var c = _bytes[_current]\n" | ||||
| "    advance()\n" | ||||
| "\n" | ||||
| "    if (!_interpolations.isEmpty) {\n" | ||||
| "      if (c == Chars.leftParen) {\n" | ||||
| "        _interpolations[-1] = _interpolations[-1] + 1\n" | ||||
| "      } else if (c == Chars.rightParen) {\n" | ||||
| "        _interpolations[-1] = _interpolations[-1] - 1\n" | ||||
| "\n" | ||||
| "        // The last \")\" in an interpolated expression ends the expression and\n" | ||||
| "        // resumes the string.\n" | ||||
| "        if (_interpolations[-1] == 0) {\n" | ||||
| "          // This is the final \")\", so the interpolation expression has ended.\n" | ||||
| "          // This \")\" now begins the next section of the template string.\n" | ||||
| "          _interpolations.removeAt(-1)\n" | ||||
| "          return readString()\n" | ||||
| "        }\n" | ||||
| "      }\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    if (PUNCTUATORS.containsKey(c)) {\n" | ||||
| "      var punctuator = PUNCTUATORS[c]\n" | ||||
| "      var type = punctuator[0]\n" | ||||
| "      var i = 1\n" | ||||
| "      while (i < punctuator.count) {\n" | ||||
| "        if (!match(punctuator[i])) break\n" | ||||
| "        type = punctuator[i + 1]\n" | ||||
| "        i = i + 2\n" | ||||
| "      }\n" | ||||
| "\n" | ||||
| "      return makeToken(type)\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    // Handle \"<\", \"<<\", and \"<=\".\n" | ||||
| "    if (c == Chars.less) {\n" | ||||
| "      if (match(Chars.less)) return makeToken(Token.lessLess)\n" | ||||
| "      if (match(Chars.equal)) return makeToken(Token.lessEqual)\n" | ||||
| "      return makeToken(Token.less)\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    // Handle \">\", \">>\", and \">=\".\n" | ||||
| "    if (c == Chars.greater) {\n" | ||||
| "      if (match(Chars.greater)) return makeToken(Token.greaterGreater)\n" | ||||
| "      if (match(Chars.equal)) return makeToken(Token.greaterEqual)\n" | ||||
| "      return makeToken(Token.greater)\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    // Handle \"/\", \"//\", and \"/*\".\n" | ||||
| "    if (c == Chars.slash) {\n" | ||||
| "      if (match(Chars.slash)) return readLineComment()\n" | ||||
| "      if (match(Chars.star)) return readBlockComment()\n" | ||||
| "      return makeToken(Token.slash)\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    if (c == Chars.underscore) return readField()\n" | ||||
| "    if (c == Chars.quote) return readString()\n" | ||||
| "\n" | ||||
| "    if (c == Chars.zero && peek() == Chars.lowerX) return readHexNumber()\n" | ||||
| "    if (Chars.isWhitespace(c)) return readWhitespace()\n" | ||||
| "    if (Chars.isDigit(c)) return readNumber()\n" | ||||
| "    if (Chars.isAlpha(c)) return readName()\n" | ||||
| "\n" | ||||
| "    return makeToken(Token.error)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Reads a line comment until the end of the line is reached.\n" | ||||
| "  readLineComment() {\n" | ||||
| "    // A line comment stops at the newline since newlines are significant.\n" | ||||
| "    while (peek() != Chars.lineFeed && !isAtEnd) {\n" | ||||
| "      advance()\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return makeToken(Token.comment)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  readBlockComment() {\n" | ||||
| "    // Block comments can nest.\n" | ||||
| "    var nesting = 1\n" | ||||
| "    while (nesting > 0) {\n" | ||||
| "      // TODO: Report error.\n" | ||||
| "      if (isAtEnd) break\n" | ||||
| "\n" | ||||
| "      if (peek() == Chars.slash && peek(1) == Chars.star) {\n" | ||||
| "        advance()\n" | ||||
| "        advance()\n" | ||||
| "        nesting = nesting + 1\n" | ||||
| "      } else if (peek() == Chars.star && peek(1) == Chars.slash) {\n" | ||||
| "        advance()\n" | ||||
| "        advance()\n" | ||||
| "        nesting = nesting - 1\n" | ||||
| "        if (nesting == 0) break\n" | ||||
| "      } else {\n" | ||||
| "        advance()\n" | ||||
| "      }\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return makeToken(Token.comment)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Reads a static or instance field.\n" | ||||
| "  readField() {\n" | ||||
| "    var type = Token.field\n" | ||||
| "\n" | ||||
| "    // Read the rest of the name.\n" | ||||
| "    while (match {|c| Chars.isAlphaNumeric(c) }) {}\n" | ||||
| "\n" | ||||
| "    return makeToken(type)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Reads a string literal.\n" | ||||
| "  readString() {\n" | ||||
| "    var type = Token.string\n" | ||||
| "\n" | ||||
| "    while (!isAtEnd) {\n" | ||||
| "      var c = _bytes[_current]\n" | ||||
| "      advance()\n" | ||||
| "\n" | ||||
| "      if (c == Chars.backslash) {\n" | ||||
| "        // TODO: Process specific escapes and validate them.\n" | ||||
| "        if (!isAtEnd) advance()\n" | ||||
| "      } else if (c == Chars.percent) {\n" | ||||
| "        // Consume the '('.\n" | ||||
| "        if (!isAtEnd) advance()\n" | ||||
| "        // TODO: Handle missing '('.\n" | ||||
| "        _interpolations.add(1)\n" | ||||
| "        type = Token.interpolation\n" | ||||
| "        break\n" | ||||
| "      } else if (c == Chars.quote) {\n" | ||||
| "        break\n" | ||||
| "      }\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return makeToken(type)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Reads a number literal.\n" | ||||
| "  readHexNumber() {\n" | ||||
| "    // Skip past the `x`.\n" | ||||
| "    advance()\n" | ||||
| "\n" | ||||
| "    // Read the rest of the number.\n" | ||||
| "    while (match {|c| Chars.isHexDigit(c) }) {}\n" | ||||
| "    return makeToken(Token.number)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Reads a series of whitespace characters.\n" | ||||
| "  readWhitespace() {\n" | ||||
| "    // Read the rest of the whitespace.\n" | ||||
| "    while (match {|c| Chars.isWhitespace(c) }) {}\n" | ||||
| "\n" | ||||
| "    return makeToken(Token.whitespace)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Reads a number literal.\n" | ||||
| "  readNumber() {\n" | ||||
| "    // Read the rest of the number.\n" | ||||
| "    while (match {|c| Chars.isDigit(c) }) {}\n" | ||||
| "\n" | ||||
| "    // TODO: Floating point, scientific.\n" | ||||
| "    return makeToken(Token.number)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Reads an identifier or keyword token.\n" | ||||
| "  readName() {\n" | ||||
| "    // Read the rest of the name.\n" | ||||
| "    while (match {|c| Chars.isAlphaNumeric(c) }) {}\n" | ||||
| "\n" | ||||
| "    var text = _source[_start..._current]\n" | ||||
| "    var type = Token.name\n" | ||||
| "    if (KEYWORDS.containsKey(text)) {\n" | ||||
| "      type = KEYWORDS[text]\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return Token.new(_source, type, _start, _current - _start)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Returns `true` if we have scanned all characters.\n" | ||||
| "  isAtEnd { _current >= _bytes.count }\n" | ||||
| "\n" | ||||
| "  // Advances past the current character.\n" | ||||
| "  advance() {\n" | ||||
| "    _current = _current + 1\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Returns the byte value of the current character.\n" | ||||
| "  peek() { peek(0) }\n" | ||||
| "\n" | ||||
| "  // Returns the byte value of the character [n] bytes past the current\n" | ||||
| "  // character.\n" | ||||
| "  peek(n) {\n" | ||||
| "    if (_current + n >= _bytes.count) return -1\n" | ||||
| "    return _bytes[_current + n]\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Consumes the current character if it matches [condition], which can be a\n" | ||||
| "  // numeric code point value or a function that takes a code point and returns\n" | ||||
| "  // `true` if the code point matches.\n" | ||||
| "  match(condition) {\n" | ||||
| "    if (isAtEnd) return false\n" | ||||
| "\n" | ||||
| "    var c = _bytes[_current]\n" | ||||
| "    if (condition is Fn) {\n" | ||||
| "      if (!condition.call(c)) return false\n" | ||||
| "    } else if (c != condition) {\n" | ||||
| "      return false\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    advance()\n" | ||||
| "    return true\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Creates a token of [type] from the current character range.\n" | ||||
| "  makeToken(type) { Token.new(_source, type, _start, _current - _start) }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "// Fire up the REPL. We use ANSI when talking to a POSIX TTY.\n" | ||||
| "if (Platform.isPosix && Stdin.isTerminal) {\n" | ||||
| "  AnsiRepl.new().run()\n" | ||||
| "} else {\n" | ||||
| "  // ANSI escape sequences probably aren't supported, so degrade.\n" | ||||
| "  SimpleRepl.new().run()\n" | ||||
| "}\n"; | ||||
							
								
								
									
										79
									
								
								src/logic/wren/module/scheduler.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/logic/wren/module/scheduler.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | |||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "uv.h" | ||||
| 
 | ||||
| #include "scheduler.h" | ||||
| #include "wren.h" | ||||
| #include "vm.h" | ||||
| 
 | ||||
| // A handle to the "Scheduler" class object. Used to call static methods on it.
 | ||||
| static WrenHandle* schedulerClass; | ||||
| 
 | ||||
| // This method resumes a fiber that is suspended waiting on an asynchronous
 | ||||
| // operation. The first resumes it with zero arguments, and the second passes
 | ||||
| // one.
 | ||||
| static WrenHandle* resume1; | ||||
| static WrenHandle* resume2; | ||||
| static WrenHandle* resumeError; | ||||
| 
 | ||||
| static void resume(WrenHandle* method) | ||||
| { | ||||
|   WrenInterpretResult result = wrenCall(getVM(), method); | ||||
|    | ||||
|   // If a runtime error occurs in response to an async operation and nothing
 | ||||
|   // catches the error in the fiber, then exit the CLI.
 | ||||
|   if (result == WREN_RESULT_RUNTIME_ERROR) | ||||
|   { | ||||
|     uv_stop(getLoop()); | ||||
|     setExitCode(70); // EX_SOFTWARE.
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void schedulerCaptureMethods(WrenVM* vm) | ||||
| { | ||||
|   wrenEnsureSlots(vm, 1); | ||||
|   wrenGetVariable(vm, "scheduler", "Scheduler", 0); | ||||
|   schedulerClass = wrenGetSlotHandle(vm, 0); | ||||
|    | ||||
|   resume1 = wrenMakeCallHandle(vm, "resume_(_)"); | ||||
|   resume2 = wrenMakeCallHandle(vm, "resume_(_,_)"); | ||||
|   resumeError = wrenMakeCallHandle(vm, "resumeError_(_,_)"); | ||||
| } | ||||
| 
 | ||||
| void schedulerResume(WrenHandle* fiber, bool hasArgument) | ||||
| { | ||||
|   WrenVM* vm = getVM(); | ||||
|   wrenEnsureSlots(vm, 2 + (hasArgument ? 1 : 0)); | ||||
|   wrenSetSlotHandle(vm, 0, schedulerClass); | ||||
|   wrenSetSlotHandle(vm, 1, fiber); | ||||
|   wrenReleaseHandle(vm, fiber); | ||||
|    | ||||
|   // If we don't need to wait for an argument to be stored on the stack, resume
 | ||||
|   // it now.
 | ||||
|   if (!hasArgument) resume(resume1); | ||||
| } | ||||
| 
 | ||||
| void schedulerFinishResume() | ||||
| { | ||||
|   resume(resume2); | ||||
| } | ||||
| 
 | ||||
| void schedulerResumeError(WrenHandle* fiber, const char* error) | ||||
| { | ||||
|   schedulerResume(fiber, true); | ||||
|   wrenSetSlotString(getVM(), 2, error); | ||||
|   resume(resumeError); | ||||
| } | ||||
| 
 | ||||
| void schedulerShutdown() | ||||
| { | ||||
|   // If the module was never loaded, we don't have anything to release.
 | ||||
|   if (schedulerClass == NULL) return; | ||||
|    | ||||
|   WrenVM* vm = getVM(); | ||||
|   wrenReleaseHandle(vm, schedulerClass); | ||||
|   wrenReleaseHandle(vm, resume1); | ||||
|   wrenReleaseHandle(vm, resume2); | ||||
|   wrenReleaseHandle(vm, resumeError); | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/logic/wren/module/scheduler.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/logic/wren/module/scheduler.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| #ifndef scheduler_h | ||||
| #define scheduler_h | ||||
| 
 | ||||
| #include "wren.h" | ||||
| 
 | ||||
| // Sets up the API stack to call one of the resume methods on Scheduler.
 | ||||
| //
 | ||||
| // If [hasArgument] is false, this just sets up the stack to have another
 | ||||
| // argument stored in slot 2 and returns. The module must store the argument
 | ||||
| // on the stack and then call [schedulerFinishResume] to complete the call.
 | ||||
| //
 | ||||
| // Otherwise, the call resumes immediately. Releases [fiber] when called.
 | ||||
| void schedulerResume(WrenHandle* fiber, bool hasArgument); | ||||
| 
 | ||||
| void schedulerFinishResume(); | ||||
| void schedulerResumeError(WrenHandle* fiber, const char* error); | ||||
| 
 | ||||
| void schedulerShutdown(); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										27
									
								
								src/logic/wren/module/scheduler.wren
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/logic/wren/module/scheduler.wren
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| class Scheduler { | ||||
|   static add(callable) { | ||||
|     if (__scheduled == null) __scheduled = [] | ||||
| 
 | ||||
|     __scheduled.add(Fiber.new { | ||||
|       callable.call() | ||||
|       runNextScheduled_() | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   // Called by native code. | ||||
|   static resume_(fiber) { fiber.transfer() } | ||||
|   static resume_(fiber, arg) { fiber.transfer(arg) } | ||||
|   static resumeError_(fiber, error) { fiber.transferError(error) } | ||||
| 
 | ||||
|   static runNextScheduled_() { | ||||
|     if (__scheduled == null || __scheduled.isEmpty) { | ||||
|       return Fiber.suspend() | ||||
|     } else { | ||||
|       return __scheduled.removeAt(0).transfer() | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   foreign static captureMethods_() | ||||
| } | ||||
| 
 | ||||
| Scheduler.captureMethods_() | ||||
							
								
								
									
										29
									
								
								src/logic/wren/module/scheduler.wren.inc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/logic/wren/module/scheduler.wren.inc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| // Generated automatically from src/module/scheduler.wren. Do not edit.
 | ||||
| static const char* schedulerModuleSource = | ||||
| "class Scheduler {\n" | ||||
| "  static add(callable) {\n" | ||||
| "    if (__scheduled == null) __scheduled = []\n" | ||||
| "\n" | ||||
| "    __scheduled.add(Fiber.new {\n" | ||||
| "      callable.call()\n" | ||||
| "      runNextScheduled_()\n" | ||||
| "    })\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  // Called by native code.\n" | ||||
| "  static resume_(fiber) { fiber.transfer() }\n" | ||||
| "  static resume_(fiber, arg) { fiber.transfer(arg) }\n" | ||||
| "  static resumeError_(fiber, error) { fiber.transferError(error) }\n" | ||||
| "\n" | ||||
| "  static runNextScheduled_() {\n" | ||||
| "    if (__scheduled == null || __scheduled.isEmpty) {\n" | ||||
| "      return Fiber.suspend()\n" | ||||
| "    } else {\n" | ||||
| "      return __scheduled.removeAt(0).transfer()\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  foreign static captureMethods_()\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "Scheduler.captureMethods_()\n"; | ||||
							
								
								
									
										39
									
								
								src/logic/wren/module/timer.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/logic/wren/module/timer.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "uv.h" | ||||
| 
 | ||||
| #include "scheduler.h" | ||||
| #include "vm.h" | ||||
| #include "wren.h" | ||||
| 
 | ||||
| // Called by libuv when the timer finished closing.
 | ||||
| static void timerCloseCallback(uv_handle_t* handle) | ||||
| { | ||||
|   free(handle); | ||||
| } | ||||
| 
 | ||||
| // Called by libuv when the timer has completed.
 | ||||
| static void timerCallback(uv_timer_t* handle) | ||||
| { | ||||
|   WrenHandle* fiber = (WrenHandle*)handle->data; | ||||
| 
 | ||||
|   // Tell libuv that we don't need the timer anymore.
 | ||||
|   uv_close((uv_handle_t*)handle, timerCloseCallback); | ||||
| 
 | ||||
|   // Run the fiber that was sleeping.
 | ||||
|   schedulerResume(fiber, false); | ||||
| } | ||||
| 
 | ||||
| void timerStartTimer(WrenVM* vm) | ||||
| { | ||||
|   int milliseconds = (int)wrenGetSlotDouble(vm, 1); | ||||
|   WrenHandle* fiber = wrenGetSlotHandle(vm, 2); | ||||
| 
 | ||||
|   // Store the fiber to resume when the timer completes.
 | ||||
|   uv_timer_t* handle = (uv_timer_t*)malloc(sizeof(uv_timer_t)); | ||||
|   handle->data = fiber; | ||||
| 
 | ||||
|   uv_timer_init(getLoop(), handle); | ||||
|   uv_timer_start(handle, timerCallback, milliseconds, 0); | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/logic/wren/module/timer.wren
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/logic/wren/module/timer.wren
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| import "scheduler" for Scheduler | ||||
| 
 | ||||
| class Timer { | ||||
|   static sleep(milliseconds) { | ||||
|     if (!(milliseconds is Num)) Fiber.abort("Milliseconds must be a number.") | ||||
|     if (milliseconds < 0) Fiber.abort("Milliseconds cannot be negative.") | ||||
| 
 | ||||
|     startTimer_(milliseconds, Fiber.current) | ||||
|     Scheduler.runNextScheduled_() | ||||
|   } | ||||
| 
 | ||||
|   foreign static startTimer_(milliseconds, fiber) | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/logic/wren/module/timer.wren.inc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/logic/wren/module/timer.wren.inc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| // Generated automatically from src/module/timer.wren. Do not edit.
 | ||||
| static const char* timerModuleSource = | ||||
| "import \"scheduler\" for Scheduler\n" | ||||
| "\n" | ||||
| "class Timer {\n" | ||||
| "  static sleep(milliseconds) {\n" | ||||
| "    if (!(milliseconds is Num)) Fiber.abort(\"Milliseconds must be a number.\")\n" | ||||
| "    if (milliseconds < 0) Fiber.abort(\"Milliseconds cannot be negative.\")\n" | ||||
| "\n" | ||||
| "    startTimer_(milliseconds, Fiber.current)\n" | ||||
| "    Scheduler.runNextScheduled_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  foreign static startTimer_(milliseconds, fiber)\n" | ||||
| "}\n"; | ||||
							
								
								
									
										96
									
								
								src/logic/wren/optional/wren_opt_meta.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/logic/wren/optional/wren_opt_meta.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | |||
| #include "wren_opt_meta.h" | ||||
| 
 | ||||
| #if WREN_OPT_META | ||||
| 
 | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "wren_vm.h" | ||||
| #include "wren_opt_meta.wren.inc" | ||||
| 
 | ||||
| void metaCompile(WrenVM* vm) | ||||
| { | ||||
|   const char* source = wrenGetSlotString(vm, 1); | ||||
|   bool isExpression = wrenGetSlotBool(vm, 2); | ||||
|   bool printErrors = wrenGetSlotBool(vm, 3); | ||||
| 
 | ||||
|   // TODO: Allow passing in module?
 | ||||
|   // Look up the module surrounding the callsite. This is brittle. The -2 walks
 | ||||
|   // up the callstack assuming that the meta module has one level of
 | ||||
|   // indirection before hitting the user's code. Any change to meta may require
 | ||||
|   // this constant to be tweaked.
 | ||||
|   ObjFiber* currentFiber = vm->fiber; | ||||
|   ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn; | ||||
|   ObjString* module = fn->module->name; | ||||
| 
 | ||||
|   ObjClosure* closure = wrenCompileSource(vm, module->value, source, | ||||
|                                           isExpression, printErrors); | ||||
|    | ||||
|   // Return the result. We can't use the public API for this since we have a
 | ||||
|   // bare ObjClosure*.
 | ||||
|   if (closure == NULL) | ||||
|   { | ||||
|     vm->apiStack[0] = NULL_VAL; | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     vm->apiStack[0] = OBJ_VAL(closure); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void metaGetModuleVariables(WrenVM* vm) | ||||
| { | ||||
|   wrenEnsureSlots(vm, 3); | ||||
|    | ||||
|   Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]); | ||||
|   if (IS_UNDEFINED(moduleValue)) | ||||
|   { | ||||
|     vm->apiStack[0] = NULL_VAL; | ||||
|     return; | ||||
|   } | ||||
|      | ||||
|   ObjModule* module = AS_MODULE(moduleValue); | ||||
|   ObjList* names = wrenNewList(vm, module->variableNames.count); | ||||
|   vm->apiStack[0] = OBJ_VAL(names); | ||||
| 
 | ||||
|   // Initialize the elements to null in case a collection happens when we
 | ||||
|   // allocate the strings below.
 | ||||
|   for (int i = 0; i < names->elements.count; i++) | ||||
|   { | ||||
|     names->elements.data[i] = NULL_VAL; | ||||
|   } | ||||
|    | ||||
|   for (int i = 0; i < names->elements.count; i++) | ||||
|   { | ||||
|     names->elements.data[i] = OBJ_VAL(module->variableNames.data[i]); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const char* wrenMetaSource() | ||||
| { | ||||
|   return metaModuleSource; | ||||
| } | ||||
| 
 | ||||
| WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, | ||||
|                                               const char* className, | ||||
|                                               bool isStatic, | ||||
|                                               const char* signature) | ||||
| { | ||||
|   // There is only one foreign method in the meta module.
 | ||||
|   ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class."); | ||||
|   ASSERT(isStatic, "Should be static."); | ||||
|    | ||||
|   if (strcmp(signature, "compile_(_,_,_)") == 0) | ||||
|   { | ||||
|     return metaCompile; | ||||
|   } | ||||
|    | ||||
|   if (strcmp(signature, "getModuleVariables_(_)") == 0) | ||||
|   { | ||||
|     return metaGetModuleVariables; | ||||
|   } | ||||
|    | ||||
|   ASSERT(false, "Unknown method."); | ||||
|   return NULL; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										18
									
								
								src/logic/wren/optional/wren_opt_meta.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/logic/wren/optional/wren_opt_meta.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| #ifndef wren_opt_meta_h | ||||
| #define wren_opt_meta_h | ||||
| 
 | ||||
| #include "wren_common.h" | ||||
| #include "wren.h" | ||||
| 
 | ||||
| // This module defines the Meta class and its associated methods.
 | ||||
| #if WREN_OPT_META | ||||
| 
 | ||||
| const char* wrenMetaSource(); | ||||
| WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm, | ||||
|                                               const char* className, | ||||
|                                               bool isStatic, | ||||
|                                               const char* signature); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										32
									
								
								src/logic/wren/optional/wren_opt_meta.wren
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/logic/wren/optional/wren_opt_meta.wren
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| class Meta { | ||||
|   static getModuleVariables(module) { | ||||
|     if (!(module is String)) Fiber.abort("Module name must be a string.") | ||||
|     var result = getModuleVariables_(module) | ||||
|     if (result != null) return result | ||||
| 
 | ||||
|     Fiber.abort("Could not find a module named '%(module)'.") | ||||
|   } | ||||
| 
 | ||||
|   static eval(source) { | ||||
|     if (!(source is String)) Fiber.abort("Source code must be a string.") | ||||
| 
 | ||||
|     var closure = compile_(source, false, false) | ||||
|     // TODO: Include compile errors. | ||||
|     if (closure == null) Fiber.abort("Could not compile source code.") | ||||
| 
 | ||||
|     closure.call() | ||||
|   } | ||||
| 
 | ||||
|   static compileExpression(source) { | ||||
|     if (!(source is String)) Fiber.abort("Source code must be a string.") | ||||
|     return compile_(source, true, true) | ||||
|   } | ||||
| 
 | ||||
|   static compile(source) { | ||||
|     if (!(source is String)) Fiber.abort("Source code must be a string.") | ||||
|     return compile_(source, false, true) | ||||
|   } | ||||
| 
 | ||||
|   foreign static compile_(source, isExpression, printErrors) | ||||
|   foreign static getModuleVariables_(module) | ||||
| } | ||||
							
								
								
									
										34
									
								
								src/logic/wren/optional/wren_opt_meta.wren.inc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/logic/wren/optional/wren_opt_meta.wren.inc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| // Generated automatically from src/optional/wren_opt_meta.wren. Do not edit.
 | ||||
| static const char* metaModuleSource = | ||||
| "class Meta {\n" | ||||
| "  static getModuleVariables(module) {\n" | ||||
| "    if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n" | ||||
| "    var result = getModuleVariables_(module)\n" | ||||
| "    if (result != null) return result\n" | ||||
| "\n" | ||||
| "    Fiber.abort(\"Could not find a module named '%(module)'.\")\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static eval(source) {\n" | ||||
| "    if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" | ||||
| "\n" | ||||
| "    var closure = compile_(source, false, false)\n" | ||||
| "    // TODO: Include compile errors.\n" | ||||
| "    if (closure == null) Fiber.abort(\"Could not compile source code.\")\n" | ||||
| "\n" | ||||
| "    closure.call()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static compileExpression(source) {\n" | ||||
| "    if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" | ||||
| "    return compile_(source, true, true)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static compile(source) {\n" | ||||
| "    if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n" | ||||
| "    return compile_(source, false, true)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  foreign static compile_(source, isExpression, printErrors)\n" | ||||
| "  foreign static getModuleVariables_(module)\n" | ||||
| "}\n"; | ||||
							
								
								
									
										144
									
								
								src/logic/wren/optional/wren_opt_random.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								src/logic/wren/optional/wren_opt_random.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | |||
| #include "wren_opt_random.h" | ||||
| 
 | ||||
| #if WREN_OPT_RANDOM | ||||
| 
 | ||||
| #include <string.h> | ||||
| #include <time.h> | ||||
| 
 | ||||
| #include "wren.h" | ||||
| #include "wren_vm.h" | ||||
| 
 | ||||
| #include "wren_opt_random.wren.inc" | ||||
| 
 | ||||
| // Implements the well equidistributed long-period linear PRNG (WELL512a).
 | ||||
| //
 | ||||
| // https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear
 | ||||
| typedef struct | ||||
| { | ||||
|   uint32_t state[16]; | ||||
|   uint32_t index; | ||||
| } Well512; | ||||
| 
 | ||||
| // Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf
 | ||||
| static uint32_t advanceState(Well512* well) | ||||
| { | ||||
|   uint32_t a, b, c, d; | ||||
|   a = well->state[well->index]; | ||||
|   c = well->state[(well->index + 13) & 15]; | ||||
|   b =  a ^ c ^ (a << 16) ^ (c << 15); | ||||
|   c = well->state[(well->index + 9) & 15]; | ||||
|   c ^= (c >> 11); | ||||
|   a = well->state[well->index] = b ^ c; | ||||
|   d = a ^ ((a << 5) & 0xda442d24U); | ||||
| 
 | ||||
|   well->index = (well->index + 15) & 15; | ||||
|   a = well->state[well->index]; | ||||
|   well->state[well->index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28); | ||||
|   return well->state[well->index]; | ||||
| } | ||||
| 
 | ||||
| static void randomAllocate(WrenVM* vm) | ||||
| { | ||||
|   Well512* well = (Well512*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Well512)); | ||||
|   well->index = 0; | ||||
| } | ||||
| 
 | ||||
| static void randomSeed0(WrenVM* vm) | ||||
| { | ||||
|   Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); | ||||
| 
 | ||||
|   srand((uint32_t)time(NULL)); | ||||
|   for (int i = 0; i < 16; i++) | ||||
|   { | ||||
|     well->state[i] = rand(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void randomSeed1(WrenVM* vm) | ||||
| { | ||||
|   Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); | ||||
| 
 | ||||
|   srand((uint32_t)wrenGetSlotDouble(vm, 1)); | ||||
|   for (int i = 0; i < 16; i++) | ||||
|   { | ||||
|     well->state[i] = rand(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void randomSeed16(WrenVM* vm) | ||||
| { | ||||
|   Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); | ||||
| 
 | ||||
|   for (int i = 0; i < 16; i++) | ||||
|   { | ||||
|     well->state[i] = (uint32_t)wrenGetSlotDouble(vm, i + 1); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void randomFloat(WrenVM* vm) | ||||
| { | ||||
|   Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); | ||||
| 
 | ||||
|   // A double has 53 bits of precision in its mantissa, and we'd like to take
 | ||||
|   // full advantage of that, so we need 53 bits of random source data.
 | ||||
| 
 | ||||
|   // First, start with 32 random bits, shifted to the left 21 bits.
 | ||||
|   double result = (double)advanceState(well) * (1 << 21); | ||||
| 
 | ||||
|   // Then add another 21 random bits.
 | ||||
|   result += (double)(advanceState(well) & ((1 << 21) - 1)); | ||||
| 
 | ||||
|   // Now we have a number from 0 - (2^53). Divide be the range to get a double
 | ||||
|   // from 0 to 1.0 (half-inclusive).
 | ||||
|   result /= 9007199254740992.0; | ||||
| 
 | ||||
|   wrenSetSlotDouble(vm, 0, result); | ||||
| } | ||||
| 
 | ||||
| static void randomInt0(WrenVM* vm) | ||||
| { | ||||
|   Well512* well = (Well512*)wrenGetSlotForeign(vm, 0); | ||||
| 
 | ||||
|   wrenSetSlotDouble(vm, 0, (double)advanceState(well)); | ||||
| } | ||||
| 
 | ||||
| const char* wrenRandomSource() | ||||
| { | ||||
|   return randomModuleSource; | ||||
| } | ||||
| 
 | ||||
| WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, | ||||
|                                                    const char* module, | ||||
|                                                    const char* className) | ||||
| { | ||||
|   ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); | ||||
|   WrenForeignClassMethods methods; | ||||
|   methods.allocate = randomAllocate; | ||||
|   methods.finalize = NULL; | ||||
|   return methods; | ||||
| } | ||||
| 
 | ||||
| WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, | ||||
|                                                 const char* className, | ||||
|                                                 bool isStatic, | ||||
|                                                 const char* signature) | ||||
| { | ||||
|   ASSERT(strcmp(className, "Random") == 0, "Should be in Random class."); | ||||
|    | ||||
|   if (strcmp(signature, "<allocate>") == 0) return randomAllocate; | ||||
|   if (strcmp(signature, "seed_()") == 0) return randomSeed0; | ||||
|   if (strcmp(signature, "seed_(_)") == 0) return randomSeed1; | ||||
|    | ||||
|   if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0) | ||||
|   { | ||||
|     return randomSeed16; | ||||
|   } | ||||
|    | ||||
|   if (strcmp(signature, "float()") == 0) return randomFloat; | ||||
|   if (strcmp(signature, "int()") == 0) return randomInt0; | ||||
|    | ||||
|   ASSERT(false, "Unknown method."); | ||||
|   return NULL; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										20
									
								
								src/logic/wren/optional/wren_opt_random.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/logic/wren/optional/wren_opt_random.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| #ifndef wren_opt_random_h | ||||
| #define wren_opt_random_h | ||||
| 
 | ||||
| #include "wren_common.h" | ||||
| #include "wren.h" | ||||
| 
 | ||||
| #if WREN_OPT_RANDOM | ||||
| 
 | ||||
| const char* wrenRandomSource(); | ||||
| WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm, | ||||
|                                                    const char* module, | ||||
|                                                    const char* className); | ||||
| WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm, | ||||
|                                                 const char* className, | ||||
|                                                 bool isStatic, | ||||
|                                                 const char* signature); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										122
									
								
								src/logic/wren/optional/wren_opt_random.wren
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/logic/wren/optional/wren_opt_random.wren
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,122 @@ | |||
| foreign class Random { | ||||
|   construct new() { | ||||
|     seed_() | ||||
|   } | ||||
| 
 | ||||
|   construct new(seed) { | ||||
|     if (seed is Num) { | ||||
|       seed_(seed) | ||||
|     } else if (seed is Sequence) { | ||||
|       if (seed.isEmpty) Fiber.abort("Sequence cannot be empty.") | ||||
| 
 | ||||
|       // TODO: Empty sequence. | ||||
|       var seeds = [] | ||||
|       for (element in seed) { | ||||
|         if (!(element is Num)) Fiber.abort("Sequence elements must all be numbers.") | ||||
| 
 | ||||
|         seeds.add(element) | ||||
|         if (seeds.count == 16) break | ||||
|       } | ||||
| 
 | ||||
|       // Cycle the values to fill in any missing slots. | ||||
|       var i = 0 | ||||
|       while (seeds.count < 16) { | ||||
|         seeds.add(seeds[i]) | ||||
|         i = i + 1 | ||||
|       } | ||||
| 
 | ||||
|       seed_( | ||||
|           seeds[0], seeds[1], seeds[2], seeds[3], | ||||
|           seeds[4], seeds[5], seeds[6], seeds[7], | ||||
|           seeds[8], seeds[9], seeds[10], seeds[11], | ||||
|           seeds[12], seeds[13], seeds[14], seeds[15]) | ||||
|     } else { | ||||
|       Fiber.abort("Seed must be a number or a sequence of numbers.") | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   foreign seed_() | ||||
|   foreign seed_(seed) | ||||
|   foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16) | ||||
| 
 | ||||
|   foreign float() | ||||
|   float(end) { float() * end } | ||||
|   float(start, end) { float() * (end - start) + start } | ||||
| 
 | ||||
|   foreign int() | ||||
|   int(end) { (float() * end).floor } | ||||
|   int(start, end) { (float() * (end - start)).floor + start } | ||||
| 
 | ||||
|   sample(list) { sample(list, 1)[0] } | ||||
|   sample(list, count) { | ||||
|     if (count > list.count) Fiber.abort("Not enough elements to sample.") | ||||
| 
 | ||||
|     // There at (at least) two simple algorithms for choosing a number of | ||||
|     // samples from a list without replacement -- where we don't pick the same | ||||
|     // element more than once. | ||||
|     // | ||||
|     // The first is faster when the number of samples is small relative to the | ||||
|     // size of the collection. In many cases, it avoids scanning the entire | ||||
|     // list. In the common case of just wanting one sample, it's a single | ||||
|     // random index lookup. | ||||
|     // | ||||
|     // However, its performance degrades badly as the sample size increases. | ||||
|     // Vitter's algorithm always scans the entire list, but it's also always | ||||
|     // O(n). | ||||
|     // | ||||
|     // The cutoff point between the two follows a quadratic curve on the same | ||||
|     // size. Based on some empirical testing, scaling that by 5 seems to fit | ||||
|     // pretty closely and chooses the fastest one for the given sample and | ||||
|     // collection size. | ||||
|     if (count * count * 5 < list.count) { | ||||
|       // Pick random elements and retry if you hit a previously chosen one. | ||||
|       var picked = {} | ||||
|       var result = [] | ||||
|       for (i in 0...count) { | ||||
|         // Find an index that we haven't already selected. | ||||
|         var index | ||||
|         while (true) { | ||||
|           index = int(count) | ||||
|           if (!picked.containsKey(index)) break | ||||
|         } | ||||
| 
 | ||||
|         picked[index] = true | ||||
|         result.add(list[index]) | ||||
|       } | ||||
| 
 | ||||
|       return result | ||||
|     } else { | ||||
|       // Jeffrey Vitter's Algorithm R. | ||||
| 
 | ||||
|       // Fill the reservoir with the first elements in the list. | ||||
|       var result = list[0...count] | ||||
| 
 | ||||
|       // We want to ensure the results are always in random order, so shuffle | ||||
|       // them. In cases where the sample size is the entire collection, this | ||||
|       // devolves to running Fisher-Yates on a copy of the list. | ||||
|       shuffle(result) | ||||
| 
 | ||||
|       // Now walk the rest of the list. For each element, randomly consider | ||||
|       // replacing one of the reservoir elements with it. The probability here | ||||
|       // works out such that it does this uniformly. | ||||
|       for (i in count...list.count) { | ||||
|         var slot = int(0, i + 1) | ||||
|         if (slot < count) result[slot] = list[i] | ||||
|       } | ||||
| 
 | ||||
|       return result | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   shuffle(list) { | ||||
|     if (list.isEmpty) return | ||||
| 
 | ||||
|     // Fisher-Yates shuffle. | ||||
|     for (i in 0...list.count - 1) { | ||||
|       var from = int(i, list.count) | ||||
|       var temp = list[from] | ||||
|       list[from] = list[i] | ||||
|       list[i] = temp | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										124
									
								
								src/logic/wren/optional/wren_opt_random.wren.inc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/logic/wren/optional/wren_opt_random.wren.inc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,124 @@ | |||
| // Generated automatically from src/optional/wren_opt_random.wren. Do not edit.
 | ||||
| static const char* randomModuleSource = | ||||
| "foreign class Random {\n" | ||||
| "  construct new() {\n" | ||||
| "    seed_()\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  construct new(seed) {\n" | ||||
| "    if (seed is Num) {\n" | ||||
| "      seed_(seed)\n" | ||||
| "    } else if (seed is Sequence) {\n" | ||||
| "      if (seed.isEmpty) Fiber.abort(\"Sequence cannot be empty.\")\n" | ||||
| "\n" | ||||
| "      // TODO: Empty sequence.\n" | ||||
| "      var seeds = []\n" | ||||
| "      for (element in seed) {\n" | ||||
| "        if (!(element is Num)) Fiber.abort(\"Sequence elements must all be numbers.\")\n" | ||||
| "\n" | ||||
| "        seeds.add(element)\n" | ||||
| "        if (seeds.count == 16) break\n" | ||||
| "      }\n" | ||||
| "\n" | ||||
| "      // Cycle the values to fill in any missing slots.\n" | ||||
| "      var i = 0\n" | ||||
| "      while (seeds.count < 16) {\n" | ||||
| "        seeds.add(seeds[i])\n" | ||||
| "        i = i + 1\n" | ||||
| "      }\n" | ||||
| "\n" | ||||
| "      seed_(\n" | ||||
| "          seeds[0], seeds[1], seeds[2], seeds[3],\n" | ||||
| "          seeds[4], seeds[5], seeds[6], seeds[7],\n" | ||||
| "          seeds[8], seeds[9], seeds[10], seeds[11],\n" | ||||
| "          seeds[12], seeds[13], seeds[14], seeds[15])\n" | ||||
| "    } else {\n" | ||||
| "      Fiber.abort(\"Seed must be a number or a sequence of numbers.\")\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  foreign seed_()\n" | ||||
| "  foreign seed_(seed)\n" | ||||
| "  foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n" | ||||
| "\n" | ||||
| "  foreign float()\n" | ||||
| "  float(end) { float() * end }\n" | ||||
| "  float(start, end) { float() * (end - start) + start }\n" | ||||
| "\n" | ||||
| "  foreign int()\n" | ||||
| "  int(end) { (float() * end).floor }\n" | ||||
| "  int(start, end) { (float() * (end - start)).floor + start }\n" | ||||
| "\n" | ||||
| "  sample(list) { sample(list, 1)[0] }\n" | ||||
| "  sample(list, count) {\n" | ||||
| "    if (count > list.count) Fiber.abort(\"Not enough elements to sample.\")\n" | ||||
| "\n" | ||||
| "    // There at (at least) two simple algorithms for choosing a number of\n" | ||||
| "    // samples from a list without replacement -- where we don't pick the same\n" | ||||
| "    // element more than once.\n" | ||||
| "    //\n" | ||||
| "    // The first is faster when the number of samples is small relative to the\n" | ||||
| "    // size of the collection. In many cases, it avoids scanning the entire\n" | ||||
| "    // list. In the common case of just wanting one sample, it's a single\n" | ||||
| "    // random index lookup.\n" | ||||
| "    //\n" | ||||
| "    // However, its performance degrades badly as the sample size increases.\n" | ||||
| "    // Vitter's algorithm always scans the entire list, but it's also always\n" | ||||
| "    // O(n).\n" | ||||
| "    //\n" | ||||
| "    // The cutoff point between the two follows a quadratic curve on the same\n" | ||||
| "    // size. Based on some empirical testing, scaling that by 5 seems to fit\n" | ||||
| "    // pretty closely and chooses the fastest one for the given sample and\n" | ||||
| "    // collection size.\n" | ||||
| "    if (count * count * 5 < list.count) {\n" | ||||
| "      // Pick random elements and retry if you hit a previously chosen one.\n" | ||||
| "      var picked = {}\n" | ||||
| "      var result = []\n" | ||||
| "      for (i in 0...count) {\n" | ||||
| "        // Find an index that we haven't already selected.\n" | ||||
| "        var index\n" | ||||
| "        while (true) {\n" | ||||
| "          index = int(count)\n" | ||||
| "          if (!picked.containsKey(index)) break\n" | ||||
| "        }\n" | ||||
| "\n" | ||||
| "        picked[index] = true\n" | ||||
| "        result.add(list[index])\n" | ||||
| "      }\n" | ||||
| "\n" | ||||
| "      return result\n" | ||||
| "    } else {\n" | ||||
| "      // Jeffrey Vitter's Algorithm R.\n" | ||||
| "\n" | ||||
| "      // Fill the reservoir with the first elements in the list.\n" | ||||
| "      var result = list[0...count]\n" | ||||
| "\n" | ||||
| "      // We want to ensure the results are always in random order, so shuffle\n" | ||||
| "      // them. In cases where the sample size is the entire collection, this\n" | ||||
| "      // devolves to running Fisher-Yates on a copy of the list.\n" | ||||
| "      shuffle(result)\n" | ||||
| "\n" | ||||
| "      // Now walk the rest of the list. For each element, randomly consider\n" | ||||
| "      // replacing one of the reservoir elements with it. The probability here\n" | ||||
| "      // works out such that it does this uniformly.\n" | ||||
| "      for (i in count...list.count) {\n" | ||||
| "        var slot = int(0, i + 1)\n" | ||||
| "        if (slot < count) result[slot] = list[i]\n" | ||||
| "      }\n" | ||||
| "\n" | ||||
| "      return result\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  shuffle(list) {\n" | ||||
| "    if (list.isEmpty) return\n" | ||||
| "\n" | ||||
| "    // Fisher-Yates shuffle.\n" | ||||
| "    for (i in 0...list.count - 1) {\n" | ||||
| "      var from = int(i, list.count)\n" | ||||
| "      var temp = list[from]\n" | ||||
| "      list[from] = list[i]\n" | ||||
| "      list[i] = temp\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "}\n"; | ||||
							
								
								
									
										202
									
								
								src/logic/wren/vm/wren_common.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/logic/wren/vm/wren_common.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,202 @@ | |||
| #ifndef wren_common_h | ||||
| #define wren_common_h | ||||
| 
 | ||||
| // This header contains macros and defines used across the entire Wren
 | ||||
| // implementation. In particular, it contains "configuration" defines that
 | ||||
| // control how Wren works. Some of these are only used while hacking on Wren
 | ||||
| // itself.
 | ||||
| //
 | ||||
| // This header is *not* intended to be included by code outside of Wren itself.
 | ||||
| 
 | ||||
| // Wren pervasively uses the C99 integer types (uint16_t, etc.) along with some
 | ||||
| // of the associated limit constants (UINT32_MAX, etc.). The constants are not
 | ||||
| // part of standard C++, so aren't included by default by C++ compilers when you
 | ||||
| // include <stdint> unless __STDC_LIMIT_MACROS is defined.
 | ||||
| #define __STDC_LIMIT_MACROS | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| // These flags let you control some details of the interpreter's implementation.
 | ||||
| // Usually they trade-off a bit of portability for speed. They default to the
 | ||||
| // most efficient behavior.
 | ||||
| 
 | ||||
| // If true, then Wren uses a NaN-tagged double for its core value
 | ||||
| // representation. Otherwise, it uses a larger more conventional struct. The
 | ||||
| // former is significantly faster and more compact. The latter is useful for
 | ||||
| // debugging and may be more portable.
 | ||||
| //
 | ||||
| // Defaults to on.
 | ||||
| #ifndef WREN_NAN_TAGGING | ||||
|   #define WREN_NAN_TAGGING 1 | ||||
| #endif | ||||
| 
 | ||||
| // If true, the VM's interpreter loop uses computed gotos. See this for more:
 | ||||
| // http://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Labels-as-Values.html
 | ||||
| // Enabling this speeds up the main dispatch loop a bit, but requires compiler
 | ||||
| // support.
 | ||||
| //
 | ||||
| // Defaults to true on supported compilers.
 | ||||
| #ifndef WREN_COMPUTED_GOTO | ||||
|   #ifdef _MSC_VER | ||||
|     // No computed gotos in Visual Studio.
 | ||||
|     #define WREN_COMPUTED_GOTO 0 | ||||
|   #else | ||||
|     #define WREN_COMPUTED_GOTO 1 | ||||
|   #endif | ||||
| #endif | ||||
| 
 | ||||
| // The VM includes a number of optional modules. You can choose to include
 | ||||
| // these or not. By default, they are all available. To disable one, set the
 | ||||
| // corresponding `WREN_OPT_<name>` define to `0`.
 | ||||
| #ifndef WREN_OPT_META | ||||
|   #define WREN_OPT_META 1 | ||||
| #endif | ||||
| 
 | ||||
| #ifndef WREN_OPT_RANDOM | ||||
|   #define WREN_OPT_RANDOM 1 | ||||
| #endif | ||||
| 
 | ||||
| // These flags are useful for debugging and hacking on Wren itself. They are not
 | ||||
| // intended to be used for production code. They default to off.
 | ||||
| 
 | ||||
| // Set this to true to stress test the GC. It will perform a collection before
 | ||||
| // every allocation. This is useful to ensure that memory is always correctly
 | ||||
| // reachable.
 | ||||
| #define WREN_DEBUG_GC_STRESS 0 | ||||
| 
 | ||||
| // Set this to true to log memory operations as they occur.
 | ||||
| #define WREN_DEBUG_TRACE_MEMORY 0 | ||||
| 
 | ||||
| // Set this to true to log garbage collections as they occur.
 | ||||
| #define WREN_DEBUG_TRACE_GC 0 | ||||
| 
 | ||||
| // Set this to true to print out the compiled bytecode of each function.
 | ||||
| #define WREN_DEBUG_DUMP_COMPILED_CODE 0 | ||||
| 
 | ||||
| // Set this to trace each instruction as it's executed.
 | ||||
| #define WREN_DEBUG_TRACE_INSTRUCTIONS 0 | ||||
| 
 | ||||
| // The maximum number of module-level variables that may be defined at one time.
 | ||||
| // This limitation comes from the 16 bits used for the arguments to
 | ||||
| // `CODE_LOAD_MODULE_VAR` and `CODE_STORE_MODULE_VAR`.
 | ||||
| #define MAX_MODULE_VARS 65536 | ||||
| 
 | ||||
| // The maximum number of arguments that can be passed to a method. Note that
 | ||||
| // this limitation is hardcoded in other places in the VM, in particular, the
 | ||||
| // `CODE_CALL_XX` instructions assume a certain maximum number.
 | ||||
| #define MAX_PARAMETERS 16 | ||||
| 
 | ||||
| // The maximum name of a method, not including the signature. This is an
 | ||||
| // arbitrary but enforced maximum just so we know how long the method name
 | ||||
| // strings need to be in the parser.
 | ||||
| #define MAX_METHOD_NAME 64 | ||||
| 
 | ||||
| // The maximum length of a method signature. Signatures look like:
 | ||||
| //
 | ||||
| //     foo        // Getter.
 | ||||
| //     foo()      // No-argument method.
 | ||||
| //     foo(_)     // One-argument method.
 | ||||
| //     foo(_,_)   // Two-argument method.
 | ||||
| //     init foo() // Constructor initializer.
 | ||||
| //
 | ||||
| // The maximum signature length takes into account the longest method name, the
 | ||||
| // 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
 | ||||
| // is so that error messages mentioning variables can be stack allocated.
 | ||||
| #define MAX_VARIABLE_NAME 64 | ||||
| 
 | ||||
| // The maximum number of fields a class can have, including inherited fields.
 | ||||
| // This is explicit in the bytecode since `CODE_CLASS` and `CODE_SUBCLASS` take
 | ||||
| // a single byte for the number of fields. Note that it's 255 and not 256
 | ||||
| // because creating a class takes the *number* of fields, not the *highest
 | ||||
| // field index*.
 | ||||
| #define MAX_FIELDS 255 | ||||
| 
 | ||||
| // Use the VM's allocator to allocate an object of [type].
 | ||||
| #define ALLOCATE(vm, type) \ | ||||
|     ((type*)wrenReallocate(vm, NULL, 0, sizeof(type))) | ||||
| 
 | ||||
| // Use the VM's allocator to allocate an object of [mainType] containing a
 | ||||
| // flexible array of [count] objects of [arrayType].
 | ||||
| #define ALLOCATE_FLEX(vm, mainType, arrayType, count) \ | ||||
|     ((mainType*)wrenReallocate(vm, NULL, 0, \ | ||||
|     sizeof(mainType) + sizeof(arrayType) * (count))) | ||||
| 
 | ||||
| // Use the VM's allocator to allocate an array of [count] elements of [type].
 | ||||
| #define ALLOCATE_ARRAY(vm, type, count) \ | ||||
|     ((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * (count))) | ||||
| 
 | ||||
| // Use the VM's allocator to free the previously allocated memory at [pointer].
 | ||||
| #define DEALLOCATE(vm, pointer) wrenReallocate(vm, pointer, 0, 0) | ||||
| 
 | ||||
| // The Microsoft compiler does not support the "inline" modifier when compiling
 | ||||
| // as plain C.
 | ||||
| #if defined( _MSC_VER ) && !defined(__cplusplus) | ||||
|   #define inline _inline | ||||
| #endif | ||||
| 
 | ||||
| // This is used to clearly mark flexible-sized arrays that appear at the end of
 | ||||
| // some dynamically-allocated structs, known as the "struct hack".
 | ||||
| #if __STDC_VERSION__ >= 199901L | ||||
|   // In C99, a flexible array member is just "[]".
 | ||||
|   #define FLEXIBLE_ARRAY | ||||
| #else | ||||
|   // Elsewhere, use a zero-sized array. It's technically undefined behavior,
 | ||||
|   // but works reliably in most known compilers.
 | ||||
|   #define FLEXIBLE_ARRAY 0 | ||||
| #endif | ||||
| 
 | ||||
| // Assertions are used to validate program invariants. They indicate things the
 | ||||
| // program expects to be true about its internal state during execution. If an
 | ||||
| // assertion fails, there is a bug in Wren.
 | ||||
| //
 | ||||
| // Assertions add significant overhead, so are only enabled in debug builds.
 | ||||
| #ifdef DEBUG | ||||
| 
 | ||||
|   #include <stdio.h> | ||||
| 
 | ||||
|   #define ASSERT(condition, message) \ | ||||
|       do \ | ||||
|       { \ | ||||
|         if (!(condition)) \ | ||||
|         { \ | ||||
|           fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \ | ||||
|               __FILE__, __LINE__, __func__, message); \ | ||||
|           abort(); \ | ||||
|         } \ | ||||
|       } \ | ||||
|       while(0) | ||||
| 
 | ||||
|   // Indicates that we know execution should never reach this point in the
 | ||||
|   // program. In debug mode, we assert this fact because it's a bug to get here.
 | ||||
|   //
 | ||||
|   // In release mode, we use compiler-specific built in functions to tell the
 | ||||
|   // compiler the code can't be reached. This avoids "missing return" warnings
 | ||||
|   // in some cases and also lets it perform some optimizations by assuming the
 | ||||
|   // code is never reached.
 | ||||
|   #define UNREACHABLE() \ | ||||
|       do \ | ||||
|       { \ | ||||
|         fprintf(stderr, "[%s:%d] This code should not be reached in %s()\n", \ | ||||
|             __FILE__, __LINE__, __func__); \ | ||||
|         abort(); \ | ||||
|       } \ | ||||
|       while (0) | ||||
| 
 | ||||
| #else | ||||
| 
 | ||||
|   #define ASSERT(condition, message) do {} while (0) | ||||
| 
 | ||||
|   // Tell the compiler that this part of the code will never be reached.
 | ||||
|   #if defined( _MSC_VER ) | ||||
|     #define UNREACHABLE() __assume(0) | ||||
|   #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) | ||||
|     #define UNREACHABLE() __builtin_unreachable() | ||||
|   #else | ||||
|     #define UNREACHABLE() | ||||
|   #endif | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										3595
									
								
								src/logic/wren/vm/wren_compiler.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3595
									
								
								src/logic/wren/vm/wren_compiler.c
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										57
									
								
								src/logic/wren/vm/wren_compiler.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/logic/wren/vm/wren_compiler.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| #ifndef wren_compiler_h | ||||
| #define wren_compiler_h | ||||
| 
 | ||||
| #include "wren.h" | ||||
| #include "wren_value.h" | ||||
| 
 | ||||
| typedef struct sCompiler Compiler; | ||||
| 
 | ||||
| // This module defines the compiler for Wren. It takes a string of source code
 | ||||
| // and lexes, parses, and compiles it. Wren uses a single-pass compiler. It
 | ||||
| // does not build an actual AST during parsing and then consume that to
 | ||||
| // generate code. Instead, the parser directly emits bytecode.
 | ||||
| //
 | ||||
| // This forces a few restrictions on the grammar and semantics of the language.
 | ||||
| // Things like forward references and arbitrary lookahead are much harder. We
 | ||||
| // get a lot in return for that, though.
 | ||||
| //
 | ||||
| // The implementation is much simpler since we don't need to define a bunch of
 | ||||
| // AST data structures. More so, we don't have to deal with managing memory for
 | ||||
| // AST objects. The compiler does almost no dynamic allocation while running.
 | ||||
| //
 | ||||
| // Compilation is also faster since we don't create a bunch of temporary data
 | ||||
| // structures and destroy them after generating code.
 | ||||
| 
 | ||||
| // Compiles [source], a string of Wren source code located in [module], to an
 | ||||
| // [ObjFn] that will execute that code when invoked. Returns `NULL` if the
 | ||||
| // source contains any syntax errors.
 | ||||
| //
 | ||||
| // If [isExpression] is `true`, [source] should be a single expression, and
 | ||||
| // this compiles it to a function that evaluates and returns that expression.
 | ||||
| // Otherwise, [source] should be a series of top level statements.
 | ||||
| //
 | ||||
| // If [printErrors] is `true`, any compile errors are output to stderr.
 | ||||
| // Otherwise, they are silently discarded.
 | ||||
| ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source, | ||||
|                    bool isExpression, bool printErrors); | ||||
| 
 | ||||
| // When a class is defined, its superclass is not known until runtime since
 | ||||
| // class definitions are just imperative statements. Most of the bytecode for a
 | ||||
| // a method doesn't care, but there are two places where it matters:
 | ||||
| //
 | ||||
| //   - To load or store a field, we need to know the index of the field in the
 | ||||
| //     instance's field array. We need to adjust this so that subclass fields
 | ||||
| //     are positioned after superclass fields, and we don't know this until the
 | ||||
| //     superclass is known.
 | ||||
| //
 | ||||
| //   - Superclass calls need to know which superclass to dispatch to.
 | ||||
| //
 | ||||
| // We could handle this dynamically, but that adds overhead. Instead, when a
 | ||||
| // method is bound, we walk the bytecode for the function and patch it up.
 | ||||
| void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn); | ||||
| 
 | ||||
| // Reaches all of the heap-allocated objects in use by [compiler] (and all of
 | ||||
| // its parents) so that they are not collected by the GC.
 | ||||
| void wrenMarkCompiler(WrenVM* vm, Compiler* compiler); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										1393
									
								
								src/logic/wren/vm/wren_core.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1393
									
								
								src/logic/wren/vm/wren_core.c
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										23
									
								
								src/logic/wren/vm/wren_core.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/logic/wren/vm/wren_core.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| #ifndef wren_core_h | ||||
| #define wren_core_h | ||||
| 
 | ||||
| #include "wren_vm.h" | ||||
| 
 | ||||
| // This module defines the built-in classes and their primitives methods that
 | ||||
| // are implemented directly in C code. Some languages try to implement as much
 | ||||
| // of the core module itself in the primary language instead of in the host
 | ||||
| // language.
 | ||||
| //
 | ||||
| // With Wren, we try to do as much of it in C as possible. Primitive methods
 | ||||
| // are always faster than code written in Wren, and it minimizes startup time
 | ||||
| // since we don't have to parse, compile, and execute Wren code.
 | ||||
| //
 | ||||
| // There is one limitation, though. Methods written in C cannot call Wren ones.
 | ||||
| // They can only be the top of the callstack, and immediately return. This
 | ||||
| // makes it difficult to have primitive methods that rely on polymorphic
 | ||||
| // behavior. For example, `IO.write` should call `toString` on its argument,
 | ||||
| // including user-defined `toString` methods on user-defined classes.
 | ||||
| 
 | ||||
| void wrenInitializeCore(WrenVM* vm); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										438
									
								
								src/logic/wren/vm/wren_core.wren
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										438
									
								
								src/logic/wren/vm/wren_core.wren
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,438 @@ | |||
| class Bool {} | ||||
| class Fiber {} | ||||
| class Fn {} | ||||
| class Null {} | ||||
| class Num {} | ||||
| 
 | ||||
| class Sequence { | ||||
|   all(f) { | ||||
|     var result = true | ||||
|     for (element in this) { | ||||
|       result = f.call(element) | ||||
|       if (!result) return result | ||||
|     } | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   any(f) { | ||||
|     var result = false | ||||
|     for (element in this) { | ||||
|       result = f.call(element) | ||||
|       if (result) return result | ||||
|     } | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   contains(element) { | ||||
|     for (item in this) { | ||||
|       if (element == item) return true | ||||
|     } | ||||
|     return false | ||||
|   } | ||||
| 
 | ||||
|   count { | ||||
|     var result = 0 | ||||
|     for (element in this) { | ||||
|       result = result + 1 | ||||
|     } | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   count(f) { | ||||
|     var result = 0 | ||||
|     for (element in this) { | ||||
|       if (f.call(element)) result = result + 1 | ||||
|     } | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   each(f) { | ||||
|     for (element in this) { | ||||
|       f.call(element) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   isEmpty { iterate(null) ? false : true } | ||||
| 
 | ||||
|   map(transformation) { MapSequence.new(this, transformation) } | ||||
| 
 | ||||
|   skip(count) { | ||||
|     if (!(count is Num) || !count.isInteger || count < 0) { | ||||
|       Fiber.abort("Count must be a non-negative integer.") | ||||
|     } | ||||
| 
 | ||||
|     return SkipSequence.new(this, count) | ||||
|   } | ||||
| 
 | ||||
|   take(count) { | ||||
|     if (!(count is Num) || !count.isInteger || count < 0) { | ||||
|       Fiber.abort("Count must be a non-negative integer.") | ||||
|     } | ||||
| 
 | ||||
|     return TakeSequence.new(this, count) | ||||
|   } | ||||
| 
 | ||||
|   where(predicate) { WhereSequence.new(this, predicate) } | ||||
| 
 | ||||
|   reduce(acc, f) { | ||||
|     for (element in this) { | ||||
|       acc = f.call(acc, element) | ||||
|     } | ||||
|     return acc | ||||
|   } | ||||
| 
 | ||||
|   reduce(f) { | ||||
|     var iter = iterate(null) | ||||
|     if (!iter) Fiber.abort("Can't reduce an empty sequence.") | ||||
| 
 | ||||
|     // Seed with the first element. | ||||
|     var result = iteratorValue(iter) | ||||
|     while (iter = iterate(iter)) { | ||||
|       result = f.call(result, iteratorValue(iter)) | ||||
|     } | ||||
| 
 | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   join() { join("") } | ||||
| 
 | ||||
|   join(sep) { | ||||
|     var first = true | ||||
|     var result = "" | ||||
| 
 | ||||
|     for (element in this) { | ||||
|       if (!first) result = result + sep | ||||
|       first = false | ||||
|       result = result + element.toString | ||||
|     } | ||||
| 
 | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   toList { | ||||
|     var result = List.new() | ||||
|     for (element in this) { | ||||
|       result.add(element) | ||||
|     } | ||||
|     return result | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class MapSequence is Sequence { | ||||
|   construct new(sequence, fn) { | ||||
|     _sequence = sequence | ||||
|     _fn = fn | ||||
|   } | ||||
| 
 | ||||
|   iterate(iterator) { _sequence.iterate(iterator) } | ||||
|   iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) } | ||||
| } | ||||
| 
 | ||||
| class SkipSequence is Sequence { | ||||
|   construct new(sequence, count) { | ||||
|     _sequence = sequence | ||||
|     _count = count | ||||
|   } | ||||
| 
 | ||||
|   iterate(iterator) { | ||||
|     if (iterator) { | ||||
|       return _sequence.iterate(iterator) | ||||
|     } else { | ||||
|       iterator = _sequence.iterate(iterator) | ||||
|       var count = _count | ||||
|       while (count > 0 && iterator) { | ||||
|         iterator = _sequence.iterate(iterator) | ||||
|         count = count - 1 | ||||
|       } | ||||
|       return iterator | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   iteratorValue(iterator) { _sequence.iteratorValue(iterator) } | ||||
| } | ||||
| 
 | ||||
| class TakeSequence is Sequence { | ||||
|   construct new(sequence, count) { | ||||
|     _sequence = sequence | ||||
|     _count = count | ||||
|   } | ||||
| 
 | ||||
|   iterate(iterator) { | ||||
|     if (!iterator) _taken = 1 else _taken = _taken + 1 | ||||
|     return _taken > _count ? null : _sequence.iterate(iterator) | ||||
|   } | ||||
| 
 | ||||
|   iteratorValue(iterator) { _sequence.iteratorValue(iterator) } | ||||
| } | ||||
| 
 | ||||
| class WhereSequence is Sequence { | ||||
|   construct new(sequence, fn) { | ||||
|     _sequence = sequence | ||||
|     _fn = fn | ||||
|   } | ||||
| 
 | ||||
|   iterate(iterator) { | ||||
|     while (iterator = _sequence.iterate(iterator)) { | ||||
|       if (_fn.call(_sequence.iteratorValue(iterator))) break | ||||
|     } | ||||
|     return iterator | ||||
|   } | ||||
| 
 | ||||
|   iteratorValue(iterator) { _sequence.iteratorValue(iterator) } | ||||
| } | ||||
| 
 | ||||
| class String is Sequence { | ||||
|   bytes { StringByteSequence.new(this) } | ||||
|   codePoints { StringCodePointSequence.new(this) } | ||||
| 
 | ||||
|   split(delimiter) { | ||||
|     if (!(delimiter is String) || delimiter.isEmpty) { | ||||
|       Fiber.abort("Delimiter must be a non-empty string.") | ||||
|     } | ||||
| 
 | ||||
|     var result = [] | ||||
| 
 | ||||
|     var last = 0 | ||||
|     var index = 0 | ||||
| 
 | ||||
|     var delimSize = delimiter.byteCount_ | ||||
|     var size = byteCount_ | ||||
| 
 | ||||
|     while (last < size && (index = indexOf(delimiter, last)) != -1) { | ||||
|       result.add(this[last...index]) | ||||
|       last = index + delimSize | ||||
|     } | ||||
| 
 | ||||
|     if (last < size) { | ||||
|       result.add(this[last..-1]) | ||||
|     } else { | ||||
|       result.add("") | ||||
|     } | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   replace(from, to) { | ||||
|     if (!(from is String) || from.isEmpty) { | ||||
|       Fiber.abort("From must be a non-empty string.") | ||||
|     } else if (!(to is String)) { | ||||
|       Fiber.abort("To must be a string.") | ||||
|     } | ||||
| 
 | ||||
|     var result = "" | ||||
| 
 | ||||
|     var last = 0 | ||||
|     var index = 0 | ||||
| 
 | ||||
|     var fromSize = from.byteCount_ | ||||
|     var size = byteCount_ | ||||
| 
 | ||||
|     while (last < size && (index = indexOf(from, last)) != -1) { | ||||
|       result = result + this[last...index] + to | ||||
|       last = index + fromSize | ||||
|     } | ||||
| 
 | ||||
|     if (last < size) result = result + this[last..-1] | ||||
| 
 | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   trim() { trim_("\t\r\n ", true, true) } | ||||
|   trim(chars) { trim_(chars, true, true) } | ||||
|   trimEnd() { trim_("\t\r\n ", false, true) } | ||||
|   trimEnd(chars) { trim_(chars, false, true) } | ||||
|   trimStart() { trim_("\t\r\n ", true, false) } | ||||
|   trimStart(chars) { trim_(chars, true, false) } | ||||
| 
 | ||||
|   trim_(chars, trimStart, trimEnd) { | ||||
|     if (!(chars is String)) { | ||||
|       Fiber.abort("Characters must be a string.") | ||||
|     } | ||||
| 
 | ||||
|     var codePoints = chars.codePoints.toList | ||||
| 
 | ||||
|     var start | ||||
|     if (trimStart) { | ||||
|       while (start = iterate(start)) { | ||||
|         if (!codePoints.contains(codePointAt_(start))) break | ||||
|       } | ||||
| 
 | ||||
|       if (start == false) return "" | ||||
|     } else { | ||||
|       start = 0 | ||||
|     } | ||||
| 
 | ||||
|     var end | ||||
|     if (trimEnd) { | ||||
|       end = byteCount_ - 1 | ||||
|       while (end >= start) { | ||||
|         var codePoint = codePointAt_(end) | ||||
|         if (codePoint != -1 && !codePoints.contains(codePoint)) break | ||||
|         end = end - 1 | ||||
|       } | ||||
| 
 | ||||
|       if (end < start) return "" | ||||
|     } else { | ||||
|       end = -1 | ||||
|     } | ||||
| 
 | ||||
|     return this[start..end] | ||||
|   } | ||||
| 
 | ||||
|   *(count) { | ||||
|     if (!(count is Num) || !count.isInteger || count < 0) { | ||||
|       Fiber.abort("Count must be a non-negative integer.") | ||||
|     } | ||||
| 
 | ||||
|     var result = "" | ||||
|     for (i in 0...count) { | ||||
|       result = result + this | ||||
|     } | ||||
|     return result | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class StringByteSequence is Sequence { | ||||
|   construct new(string) { | ||||
|     _string = string | ||||
|   } | ||||
| 
 | ||||
|   [index] { _string.byteAt_(index) } | ||||
|   iterate(iterator) { _string.iterateByte_(iterator) } | ||||
|   iteratorValue(iterator) { _string.byteAt_(iterator) } | ||||
| 
 | ||||
|   count { _string.byteCount_ } | ||||
| } | ||||
| 
 | ||||
| class StringCodePointSequence is Sequence { | ||||
|   construct new(string) { | ||||
|     _string = string | ||||
|   } | ||||
| 
 | ||||
|   [index] { _string.codePointAt_(index) } | ||||
|   iterate(iterator) { _string.iterate(iterator) } | ||||
|   iteratorValue(iterator) { _string.codePointAt_(iterator) } | ||||
| 
 | ||||
|   count { _string.count } | ||||
| } | ||||
| 
 | ||||
| class List is Sequence { | ||||
|   addAll(other) { | ||||
|     for (element in other) { | ||||
|       add(element) | ||||
|     } | ||||
|     return other | ||||
|   } | ||||
| 
 | ||||
|   toString { "[%(join(", "))]" } | ||||
| 
 | ||||
|   +(other) { | ||||
|     var result = this[0..-1] | ||||
|     for (element in other) { | ||||
|       result.add(element) | ||||
|     } | ||||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   *(count) { | ||||
|     if (!(count is Num) || !count.isInteger || count < 0) { | ||||
|       Fiber.abort("Count must be a non-negative integer.") | ||||
|     } | ||||
| 
 | ||||
|     var result = [] | ||||
|     for (i in 0...count) { | ||||
|       result.addAll(this) | ||||
|     } | ||||
|     return result | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class Map is Sequence { | ||||
|   keys { MapKeySequence.new(this) } | ||||
|   values { MapValueSequence.new(this) } | ||||
| 
 | ||||
|   toString { | ||||
|     var first = true | ||||
|     var result = "{" | ||||
| 
 | ||||
|     for (key in keys) { | ||||
|       if (!first) result = result + ", " | ||||
|       first = false | ||||
|       result = result + "%(key): %(this[key])" | ||||
|     } | ||||
| 
 | ||||
|     return result + "}" | ||||
|   } | ||||
| 
 | ||||
|   iteratorValue(iterator) { | ||||
|     return MapEntry.new( | ||||
|         keyIteratorValue_(iterator), | ||||
|         valueIteratorValue_(iterator)) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class MapEntry { | ||||
|   construct new(key, value) { | ||||
|     _key = key | ||||
|     _value = value | ||||
|   } | ||||
| 
 | ||||
|   key { _key } | ||||
|   value { _value } | ||||
| 
 | ||||
|   toString { "%(_key):%(_value)" } | ||||
| } | ||||
| 
 | ||||
| class MapKeySequence is Sequence { | ||||
|   construct new(map) { | ||||
|     _map = map | ||||
|   } | ||||
| 
 | ||||
|   iterate(n) { _map.iterate(n) } | ||||
|   iteratorValue(iterator) { _map.keyIteratorValue_(iterator) } | ||||
| } | ||||
| 
 | ||||
| class MapValueSequence is Sequence { | ||||
|   construct new(map) { | ||||
|     _map = map | ||||
|   } | ||||
| 
 | ||||
|   iterate(n) { _map.iterate(n) } | ||||
|   iteratorValue(iterator) { _map.valueIteratorValue_(iterator) } | ||||
| } | ||||
| 
 | ||||
| class Range is Sequence {} | ||||
| 
 | ||||
| class System { | ||||
|   static print() { | ||||
|     writeString_("\n") | ||||
|   } | ||||
| 
 | ||||
|   static print(obj) { | ||||
|     writeObject_(obj) | ||||
|     writeString_("\n") | ||||
|     return obj | ||||
|   } | ||||
| 
 | ||||
|   static printAll(sequence) { | ||||
|     for (object in sequence) writeObject_(object) | ||||
|     writeString_("\n") | ||||
|   } | ||||
| 
 | ||||
|   static write(obj) { | ||||
|     writeObject_(obj) | ||||
|     return obj | ||||
|   } | ||||
| 
 | ||||
|   static writeAll(sequence) { | ||||
|     for (object in sequence) writeObject_(object) | ||||
|   } | ||||
| 
 | ||||
|   static writeObject_(obj) { | ||||
|     var string = obj.toString | ||||
|     if (string is String) { | ||||
|       writeString_(string) | ||||
|     } else { | ||||
|       writeString_("[invalid toString]") | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										440
									
								
								src/logic/wren/vm/wren_core.wren.inc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										440
									
								
								src/logic/wren/vm/wren_core.wren.inc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,440 @@ | |||
| // Generated automatically from src/vm/wren_core.wren. Do not edit.
 | ||||
| static const char* coreModuleSource = | ||||
| "class Bool {}\n" | ||||
| "class Fiber {}\n" | ||||
| "class Fn {}\n" | ||||
| "class Null {}\n" | ||||
| "class Num {}\n" | ||||
| "\n" | ||||
| "class Sequence {\n" | ||||
| "  all(f) {\n" | ||||
| "    var result = true\n" | ||||
| "    for (element in this) {\n" | ||||
| "      result = f.call(element)\n" | ||||
| "      if (!result) return result\n" | ||||
| "    }\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  any(f) {\n" | ||||
| "    var result = false\n" | ||||
| "    for (element in this) {\n" | ||||
| "      result = f.call(element)\n" | ||||
| "      if (result) return result\n" | ||||
| "    }\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  contains(element) {\n" | ||||
| "    for (item in this) {\n" | ||||
| "      if (element == item) return true\n" | ||||
| "    }\n" | ||||
| "    return false\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  count {\n" | ||||
| "    var result = 0\n" | ||||
| "    for (element in this) {\n" | ||||
| "      result = result + 1\n" | ||||
| "    }\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  count(f) {\n" | ||||
| "    var result = 0\n" | ||||
| "    for (element in this) {\n" | ||||
| "      if (f.call(element)) result = result + 1\n" | ||||
| "    }\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  each(f) {\n" | ||||
| "    for (element in this) {\n" | ||||
| "      f.call(element)\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  isEmpty { iterate(null) ? false : true }\n" | ||||
| "\n" | ||||
| "  map(transformation) { MapSequence.new(this, transformation) }\n" | ||||
| "\n" | ||||
| "  skip(count) {\n" | ||||
| "    if (!(count is Num) || !count.isInteger || count < 0) {\n" | ||||
| "      Fiber.abort(\"Count must be a non-negative integer.\")\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return SkipSequence.new(this, count)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  take(count) {\n" | ||||
| "    if (!(count is Num) || !count.isInteger || count < 0) {\n" | ||||
| "      Fiber.abort(\"Count must be a non-negative integer.\")\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return TakeSequence.new(this, count)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  where(predicate) { WhereSequence.new(this, predicate) }\n" | ||||
| "\n" | ||||
| "  reduce(acc, f) {\n" | ||||
| "    for (element in this) {\n" | ||||
| "      acc = f.call(acc, element)\n" | ||||
| "    }\n" | ||||
| "    return acc\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  reduce(f) {\n" | ||||
| "    var iter = iterate(null)\n" | ||||
| "    if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n" | ||||
| "\n" | ||||
| "    // Seed with the first element.\n" | ||||
| "    var result = iteratorValue(iter)\n" | ||||
| "    while (iter = iterate(iter)) {\n" | ||||
| "      result = f.call(result, iteratorValue(iter))\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  join() { join(\"\") }\n" | ||||
| "\n" | ||||
| "  join(sep) {\n" | ||||
| "    var first = true\n" | ||||
| "    var result = \"\"\n" | ||||
| "\n" | ||||
| "    for (element in this) {\n" | ||||
| "      if (!first) result = result + sep\n" | ||||
| "      first = false\n" | ||||
| "      result = result + element.toString\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  toList {\n" | ||||
| "    var result = List.new()\n" | ||||
| "    for (element in this) {\n" | ||||
| "      result.add(element)\n" | ||||
| "    }\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class MapSequence is Sequence {\n" | ||||
| "  construct new(sequence, fn) {\n" | ||||
| "    _sequence = sequence\n" | ||||
| "    _fn = fn\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iterate(iterator) { _sequence.iterate(iterator) }\n" | ||||
| "  iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class SkipSequence is Sequence {\n" | ||||
| "  construct new(sequence, count) {\n" | ||||
| "    _sequence = sequence\n" | ||||
| "    _count = count\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iterate(iterator) {\n" | ||||
| "    if (iterator) {\n" | ||||
| "      return _sequence.iterate(iterator)\n" | ||||
| "    } else {\n" | ||||
| "      iterator = _sequence.iterate(iterator)\n" | ||||
| "      var count = _count\n" | ||||
| "      while (count > 0 && iterator) {\n" | ||||
| "        iterator = _sequence.iterate(iterator)\n" | ||||
| "        count = count - 1\n" | ||||
| "      }\n" | ||||
| "      return iterator\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class TakeSequence is Sequence {\n" | ||||
| "  construct new(sequence, count) {\n" | ||||
| "    _sequence = sequence\n" | ||||
| "    _count = count\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iterate(iterator) {\n" | ||||
| "    if (!iterator) _taken = 1 else _taken = _taken + 1\n" | ||||
| "    return _taken > _count ? null : _sequence.iterate(iterator)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class WhereSequence is Sequence {\n" | ||||
| "  construct new(sequence, fn) {\n" | ||||
| "    _sequence = sequence\n" | ||||
| "    _fn = fn\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iterate(iterator) {\n" | ||||
| "    while (iterator = _sequence.iterate(iterator)) {\n" | ||||
| "      if (_fn.call(_sequence.iteratorValue(iterator))) break\n" | ||||
| "    }\n" | ||||
| "    return iterator\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class String is Sequence {\n" | ||||
| "  bytes { StringByteSequence.new(this) }\n" | ||||
| "  codePoints { StringCodePointSequence.new(this) }\n" | ||||
| "\n" | ||||
| "  split(delimiter) {\n" | ||||
| "    if (!(delimiter is String) || delimiter.isEmpty) {\n" | ||||
| "      Fiber.abort(\"Delimiter must be a non-empty string.\")\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    var result = []\n" | ||||
| "\n" | ||||
| "    var last = 0\n" | ||||
| "    var index = 0\n" | ||||
| "\n" | ||||
| "    var delimSize = delimiter.byteCount_\n" | ||||
| "    var size = byteCount_\n" | ||||
| "\n" | ||||
| "    while (last < size && (index = indexOf(delimiter, last)) != -1) {\n" | ||||
| "      result.add(this[last...index])\n" | ||||
| "      last = index + delimSize\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    if (last < size) {\n" | ||||
| "      result.add(this[last..-1])\n" | ||||
| "    } else {\n" | ||||
| "      result.add(\"\")\n" | ||||
| "    }\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  replace(from, to) {\n" | ||||
| "    if (!(from is String) || from.isEmpty) {\n" | ||||
| "      Fiber.abort(\"From must be a non-empty string.\")\n" | ||||
| "    } else if (!(to is String)) {\n" | ||||
| "      Fiber.abort(\"To must be a string.\")\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    var result = \"\"\n" | ||||
| "\n" | ||||
| "    var last = 0\n" | ||||
| "    var index = 0\n" | ||||
| "\n" | ||||
| "    var fromSize = from.byteCount_\n" | ||||
| "    var size = byteCount_\n" | ||||
| "\n" | ||||
| "    while (last < size && (index = indexOf(from, last)) != -1) {\n" | ||||
| "      result = result + this[last...index] + to\n" | ||||
| "      last = index + fromSize\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    if (last < size) result = result + this[last..-1]\n" | ||||
| "\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  trim() { trim_(\"\t\r\n \", true, true) }\n" | ||||
| "  trim(chars) { trim_(chars, true, true) }\n" | ||||
| "  trimEnd() { trim_(\"\t\r\n \", false, true) }\n" | ||||
| "  trimEnd(chars) { trim_(chars, false, true) }\n" | ||||
| "  trimStart() { trim_(\"\t\r\n \", true, false) }\n" | ||||
| "  trimStart(chars) { trim_(chars, true, false) }\n" | ||||
| "\n" | ||||
| "  trim_(chars, trimStart, trimEnd) {\n" | ||||
| "    if (!(chars is String)) {\n" | ||||
| "      Fiber.abort(\"Characters must be a string.\")\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    var codePoints = chars.codePoints.toList\n" | ||||
| "\n" | ||||
| "    var start\n" | ||||
| "    if (trimStart) {\n" | ||||
| "      while (start = iterate(start)) {\n" | ||||
| "        if (!codePoints.contains(codePointAt_(start))) break\n" | ||||
| "      }\n" | ||||
| "\n" | ||||
| "      if (start == false) return \"\"\n" | ||||
| "    } else {\n" | ||||
| "      start = 0\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    var end\n" | ||||
| "    if (trimEnd) {\n" | ||||
| "      end = byteCount_ - 1\n" | ||||
| "      while (end >= start) {\n" | ||||
| "        var codePoint = codePointAt_(end)\n" | ||||
| "        if (codePoint != -1 && !codePoints.contains(codePoint)) break\n" | ||||
| "        end = end - 1\n" | ||||
| "      }\n" | ||||
| "\n" | ||||
| "      if (end < start) return \"\"\n" | ||||
| "    } else {\n" | ||||
| "      end = -1\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return this[start..end]\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  *(count) {\n" | ||||
| "    if (!(count is Num) || !count.isInteger || count < 0) {\n" | ||||
| "      Fiber.abort(\"Count must be a non-negative integer.\")\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    var result = \"\"\n" | ||||
| "    for (i in 0...count) {\n" | ||||
| "      result = result + this\n" | ||||
| "    }\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class StringByteSequence is Sequence {\n" | ||||
| "  construct new(string) {\n" | ||||
| "    _string = string\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  [index] { _string.byteAt_(index) }\n" | ||||
| "  iterate(iterator) { _string.iterateByte_(iterator) }\n" | ||||
| "  iteratorValue(iterator) { _string.byteAt_(iterator) }\n" | ||||
| "\n" | ||||
| "  count { _string.byteCount_ }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class StringCodePointSequence is Sequence {\n" | ||||
| "  construct new(string) {\n" | ||||
| "    _string = string\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  [index] { _string.codePointAt_(index) }\n" | ||||
| "  iterate(iterator) { _string.iterate(iterator) }\n" | ||||
| "  iteratorValue(iterator) { _string.codePointAt_(iterator) }\n" | ||||
| "\n" | ||||
| "  count { _string.count }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class List is Sequence {\n" | ||||
| "  addAll(other) {\n" | ||||
| "    for (element in other) {\n" | ||||
| "      add(element)\n" | ||||
| "    }\n" | ||||
| "    return other\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  toString { \"[%(join(\", \"))]\" }\n" | ||||
| "\n" | ||||
| "  +(other) {\n" | ||||
| "    var result = this[0..-1]\n" | ||||
| "    for (element in other) {\n" | ||||
| "      result.add(element)\n" | ||||
| "    }\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  *(count) {\n" | ||||
| "    if (!(count is Num) || !count.isInteger || count < 0) {\n" | ||||
| "      Fiber.abort(\"Count must be a non-negative integer.\")\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    var result = []\n" | ||||
| "    for (i in 0...count) {\n" | ||||
| "      result.addAll(this)\n" | ||||
| "    }\n" | ||||
| "    return result\n" | ||||
| "  }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class Map is Sequence {\n" | ||||
| "  keys { MapKeySequence.new(this) }\n" | ||||
| "  values { MapValueSequence.new(this) }\n" | ||||
| "\n" | ||||
| "  toString {\n" | ||||
| "    var first = true\n" | ||||
| "    var result = \"{\"\n" | ||||
| "\n" | ||||
| "    for (key in keys) {\n" | ||||
| "      if (!first) result = result + \", \"\n" | ||||
| "      first = false\n" | ||||
| "      result = result + \"%(key): %(this[key])\"\n" | ||||
| "    }\n" | ||||
| "\n" | ||||
| "    return result + \"}\"\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iteratorValue(iterator) {\n" | ||||
| "    return MapEntry.new(\n" | ||||
| "        keyIteratorValue_(iterator),\n" | ||||
| "        valueIteratorValue_(iterator))\n" | ||||
| "  }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class MapEntry {\n" | ||||
| "  construct new(key, value) {\n" | ||||
| "    _key = key\n" | ||||
| "    _value = value\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  key { _key }\n" | ||||
| "  value { _value }\n" | ||||
| "\n" | ||||
| "  toString { \"%(_key):%(_value)\" }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class MapKeySequence is Sequence {\n" | ||||
| "  construct new(map) {\n" | ||||
| "    _map = map\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iterate(n) { _map.iterate(n) }\n" | ||||
| "  iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class MapValueSequence is Sequence {\n" | ||||
| "  construct new(map) {\n" | ||||
| "    _map = map\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  iterate(n) { _map.iterate(n) }\n" | ||||
| "  iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n" | ||||
| "}\n" | ||||
| "\n" | ||||
| "class Range is Sequence {}\n" | ||||
| "\n" | ||||
| "class System {\n" | ||||
| "  static print() {\n" | ||||
| "    writeString_(\"\n\")\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static print(obj) {\n" | ||||
| "    writeObject_(obj)\n" | ||||
| "    writeString_(\"\n\")\n" | ||||
| "    return obj\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static printAll(sequence) {\n" | ||||
| "    for (object in sequence) writeObject_(object)\n" | ||||
| "    writeString_(\"\n\")\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static write(obj) {\n" | ||||
| "    writeObject_(obj)\n" | ||||
| "    return obj\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static writeAll(sequence) {\n" | ||||
| "    for (object in sequence) writeObject_(object)\n" | ||||
| "  }\n" | ||||
| "\n" | ||||
| "  static writeObject_(obj) {\n" | ||||
| "    var string = obj.toString\n" | ||||
| "    if (string is String) {\n" | ||||
| "      writeString_(string)\n" | ||||
| "    } else {\n" | ||||
| "      writeString_(\"[invalid toString]\")\n" | ||||
| "    }\n" | ||||
| "  }\n" | ||||
| "}\n"; | ||||
							
								
								
									
										387
									
								
								src/logic/wren/vm/wren_debug.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								src/logic/wren/vm/wren_debug.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,387 @@ | |||
| #include <stdio.h> | ||||
| 
 | ||||
| #include "wren_debug.h" | ||||
| 
 | ||||
| void wrenDebugPrintStackTrace(WrenVM* vm) | ||||
| { | ||||
|   // Bail if the host doesn't enable printing errors.
 | ||||
|   if (vm->config.errorFn == NULL) return; | ||||
|    | ||||
|   ObjFiber* fiber = vm->fiber; | ||||
|   if (IS_STRING(fiber->error)) | ||||
|   { | ||||
|     vm->config.errorFn(vm, WREN_ERROR_RUNTIME, | ||||
|                        NULL, -1, AS_CSTRING(fiber->error)); | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     // TODO: Print something a little useful here. Maybe the name of the error's
 | ||||
|     // class?
 | ||||
|     vm->config.errorFn(vm, WREN_ERROR_RUNTIME, | ||||
|                        NULL, -1, "[error object]"); | ||||
|   } | ||||
| 
 | ||||
|   for (int i = fiber->numFrames - 1; i >= 0; i--) | ||||
|   { | ||||
|     CallFrame* frame = &fiber->frames[i]; | ||||
|     ObjFn* fn = frame->closure->fn; | ||||
| 
 | ||||
|     // Skip over stub functions for calling methods from the C API.
 | ||||
|     if (fn->module == NULL) continue; | ||||
|      | ||||
|     // The built-in core module has no name. We explicitly omit it from stack
 | ||||
|     // traces since we don't want to highlight to a user the implementation
 | ||||
|     // detail of what part of the core module is written in C and what is Wren.
 | ||||
|     if (fn->module->name == NULL) continue; | ||||
|      | ||||
|     // -1 because IP has advanced past the instruction that it just executed.
 | ||||
|     int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1]; | ||||
|     vm->config.errorFn(vm, WREN_ERROR_STACK_TRACE, | ||||
|                        fn->module->name->value, line, | ||||
|                        fn->debug->name); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void dumpObject(Obj* obj) | ||||
| { | ||||
|   switch (obj->type) | ||||
|   { | ||||
|     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; | ||||
|     case OBJ_MODULE: printf("[module %p]", obj); break; | ||||
|     case OBJ_RANGE: printf("[range %p]", obj); break; | ||||
|     case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break; | ||||
|     case OBJ_UPVALUE: printf("[upvalue %p]", obj); break; | ||||
|     default: printf("[unknown object %d]", obj->type); break; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void wrenDumpValue(Value value) | ||||
| { | ||||
| #if WREN_NAN_TAGGING | ||||
|   if (IS_NUM(value)) | ||||
|   { | ||||
|     printf("%.14g", AS_NUM(value)); | ||||
|   } | ||||
|   else if (IS_OBJ(value)) | ||||
|   { | ||||
|     dumpObject(AS_OBJ(value)); | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     switch (GET_TAG(value)) | ||||
|     { | ||||
|       case TAG_FALSE:     printf("false"); break; | ||||
|       case TAG_NAN:       printf("NaN"); break; | ||||
|       case TAG_NULL:      printf("null"); break; | ||||
|       case TAG_TRUE:      printf("true"); break; | ||||
|       case TAG_UNDEFINED: UNREACHABLE(); | ||||
|     } | ||||
|   } | ||||
| #else | ||||
|   switch (value.type) | ||||
|   { | ||||
|     case VAL_FALSE:     printf("false"); break; | ||||
|     case VAL_NULL:      printf("null"); break; | ||||
|     case VAL_NUM:       printf("%.14g", AS_NUM(value)); break; | ||||
|     case VAL_TRUE:      printf("true"); break; | ||||
|     case VAL_OBJ:       dumpObject(AS_OBJ(value)); break; | ||||
|     case VAL_UNDEFINED: UNREACHABLE(); | ||||
|   } | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) | ||||
| { | ||||
|   int start = i; | ||||
|   uint8_t* bytecode = fn->code.data; | ||||
|   Code code = (Code)bytecode[i]; | ||||
| 
 | ||||
|   int line = fn->debug->sourceLines.data[i]; | ||||
|   if (lastLine == NULL || *lastLine != line) | ||||
|   { | ||||
|     printf("%4d:", line); | ||||
|     if (lastLine != NULL) *lastLine = line; | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     printf("     "); | ||||
|   } | ||||
| 
 | ||||
|   printf(" %04d  ", i++); | ||||
| 
 | ||||
|   #define READ_BYTE() (bytecode[i++]) | ||||
|   #define READ_SHORT() (i += 2, (bytecode[i - 2] << 8) | bytecode[i - 1]) | ||||
| 
 | ||||
|   #define BYTE_INSTRUCTION(name) \ | ||||
|       printf("%-16s %5d\n", name, READ_BYTE()); \ | ||||
|       break; \ | ||||
| 
 | ||||
|   switch (code) | ||||
|   { | ||||
|     case CODE_CONSTANT: | ||||
|     { | ||||
|       int constant = READ_SHORT(); | ||||
|       printf("%-16s %5d '", "CONSTANT", constant); | ||||
|       wrenDumpValue(fn->constants.data[constant]); | ||||
|       printf("'\n"); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_NULL:  printf("NULL\n"); break; | ||||
|     case CODE_FALSE: printf("FALSE\n"); break; | ||||
|     case CODE_TRUE:  printf("TRUE\n"); break; | ||||
| 
 | ||||
|     case CODE_LOAD_LOCAL_0: printf("LOAD_LOCAL_0\n"); break; | ||||
|     case CODE_LOAD_LOCAL_1: printf("LOAD_LOCAL_1\n"); break; | ||||
|     case CODE_LOAD_LOCAL_2: printf("LOAD_LOCAL_2\n"); break; | ||||
|     case CODE_LOAD_LOCAL_3: printf("LOAD_LOCAL_3\n"); break; | ||||
|     case CODE_LOAD_LOCAL_4: printf("LOAD_LOCAL_4\n"); break; | ||||
|     case CODE_LOAD_LOCAL_5: printf("LOAD_LOCAL_5\n"); break; | ||||
|     case CODE_LOAD_LOCAL_6: printf("LOAD_LOCAL_6\n"); break; | ||||
|     case CODE_LOAD_LOCAL_7: printf("LOAD_LOCAL_7\n"); break; | ||||
|     case CODE_LOAD_LOCAL_8: printf("LOAD_LOCAL_8\n"); break; | ||||
| 
 | ||||
|     case CODE_LOAD_LOCAL: BYTE_INSTRUCTION("LOAD_LOCAL"); | ||||
|     case CODE_STORE_LOCAL: BYTE_INSTRUCTION("STORE_LOCAL"); | ||||
|     case CODE_LOAD_UPVALUE: BYTE_INSTRUCTION("LOAD_UPVALUE"); | ||||
|     case CODE_STORE_UPVALUE: BYTE_INSTRUCTION("STORE_UPVALUE"); | ||||
| 
 | ||||
|     case CODE_LOAD_MODULE_VAR: | ||||
|     { | ||||
|       int slot = READ_SHORT(); | ||||
|       printf("%-16s %5d '%s'\n", "LOAD_MODULE_VAR", slot, | ||||
|              fn->module->variableNames.data[slot]->value); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_STORE_MODULE_VAR: | ||||
|     { | ||||
|       int slot = READ_SHORT(); | ||||
|       printf("%-16s %5d '%s'\n", "STORE_MODULE_VAR", slot, | ||||
|              fn->module->variableNames.data[slot]->value); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_LOAD_FIELD_THIS: BYTE_INSTRUCTION("LOAD_FIELD_THIS"); | ||||
|     case CODE_STORE_FIELD_THIS: BYTE_INSTRUCTION("STORE_FIELD_THIS"); | ||||
|     case CODE_LOAD_FIELD: BYTE_INSTRUCTION("LOAD_FIELD"); | ||||
|     case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD"); | ||||
| 
 | ||||
|     case CODE_POP: printf("POP\n"); break; | ||||
| 
 | ||||
|     case CODE_CALL_0: | ||||
|     case CODE_CALL_1: | ||||
|     case CODE_CALL_2: | ||||
|     case CODE_CALL_3: | ||||
|     case CODE_CALL_4: | ||||
|     case CODE_CALL_5: | ||||
|     case CODE_CALL_6: | ||||
|     case CODE_CALL_7: | ||||
|     case CODE_CALL_8: | ||||
|     case CODE_CALL_9: | ||||
|     case CODE_CALL_10: | ||||
|     case CODE_CALL_11: | ||||
|     case CODE_CALL_12: | ||||
|     case CODE_CALL_13: | ||||
|     case CODE_CALL_14: | ||||
|     case CODE_CALL_15: | ||||
|     case CODE_CALL_16: | ||||
|     { | ||||
|       int numArgs = bytecode[i - 1] - CODE_CALL_0; | ||||
|       int symbol = READ_SHORT(); | ||||
|       printf("CALL_%-11d %5d '%s'\n", numArgs, symbol, | ||||
|              vm->methodNames.data[symbol]->value); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_SUPER_0: | ||||
|     case CODE_SUPER_1: | ||||
|     case CODE_SUPER_2: | ||||
|     case CODE_SUPER_3: | ||||
|     case CODE_SUPER_4: | ||||
|     case CODE_SUPER_5: | ||||
|     case CODE_SUPER_6: | ||||
|     case CODE_SUPER_7: | ||||
|     case CODE_SUPER_8: | ||||
|     case CODE_SUPER_9: | ||||
|     case CODE_SUPER_10: | ||||
|     case CODE_SUPER_11: | ||||
|     case CODE_SUPER_12: | ||||
|     case CODE_SUPER_13: | ||||
|     case CODE_SUPER_14: | ||||
|     case CODE_SUPER_15: | ||||
|     case CODE_SUPER_16: | ||||
|     { | ||||
|       int numArgs = bytecode[i - 1] - CODE_SUPER_0; | ||||
|       int symbol = READ_SHORT(); | ||||
|       int superclass = READ_SHORT(); | ||||
|       printf("SUPER_%-10d %5d '%s' %5d\n", numArgs, symbol, | ||||
|              vm->methodNames.data[symbol]->value, superclass); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_JUMP: | ||||
|     { | ||||
|       int offset = READ_SHORT(); | ||||
|       printf("%-16s %5d to %d\n", "JUMP", offset, i + offset); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_LOOP: | ||||
|     { | ||||
|       int offset = READ_SHORT(); | ||||
|       printf("%-16s %5d to %d\n", "LOOP", offset, i - offset); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_JUMP_IF: | ||||
|     { | ||||
|       int offset = READ_SHORT(); | ||||
|       printf("%-16s %5d to %d\n", "JUMP_IF", offset, i + offset); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_AND: | ||||
|     { | ||||
|       int offset = READ_SHORT(); | ||||
|       printf("%-16s %5d to %d\n", "AND", offset, i + offset); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_OR: | ||||
|     { | ||||
|       int offset = READ_SHORT(); | ||||
|       printf("%-16s %5d to %d\n", "OR", offset, i + offset); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_CLOSE_UPVALUE: printf("CLOSE_UPVALUE\n"); break; | ||||
|     case CODE_RETURN:        printf("RETURN\n"); break; | ||||
| 
 | ||||
|     case CODE_CLOSURE: | ||||
|     { | ||||
|       int constant = READ_SHORT(); | ||||
|       printf("%-16s %5d ", "CLOSURE", constant); | ||||
|       wrenDumpValue(fn->constants.data[constant]); | ||||
|       printf(" "); | ||||
|       ObjFn* loadedFn = AS_FN(fn->constants.data[constant]); | ||||
|       for (int j = 0; j < loadedFn->numUpvalues; j++) | ||||
|       { | ||||
|         int isLocal = READ_BYTE(); | ||||
|         int index = READ_BYTE(); | ||||
|         if (j > 0) printf(", "); | ||||
|         printf("%s %d", isLocal ? "local" : "upvalue", index); | ||||
|       } | ||||
|       printf("\n"); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_CONSTRUCT:         printf("CONSTRUCT\n"); break; | ||||
|     case CODE_FOREIGN_CONSTRUCT: printf("FOREIGN_CONSTRUCT\n"); break; | ||||
|        | ||||
|     case CODE_CLASS: | ||||
|     { | ||||
|       int numFields = READ_BYTE(); | ||||
|       printf("%-16s %5d fields\n", "CLASS", numFields); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break; | ||||
| 
 | ||||
|     case CODE_METHOD_INSTANCE: | ||||
|     { | ||||
|       int symbol = READ_SHORT(); | ||||
|       printf("%-16s %5d '%s'\n", "METHOD_INSTANCE", symbol, | ||||
|              vm->methodNames.data[symbol]->value); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     case CODE_METHOD_STATIC: | ||||
|     { | ||||
|       int symbol = READ_SHORT(); | ||||
|       printf("%-16s %5d '%s'\n", "METHOD_STATIC", symbol, | ||||
|              vm->methodNames.data[symbol]->value); | ||||
|       break; | ||||
|     } | ||||
|        | ||||
|     case CODE_END_MODULE: | ||||
|       printf("END_MODULE\n"); | ||||
|       break; | ||||
|        | ||||
|     case CODE_IMPORT_MODULE: | ||||
|     { | ||||
|       int name = READ_SHORT(); | ||||
|       printf("%-16s %5d '", "IMPORT_MODULE", name); | ||||
|       wrenDumpValue(fn->constants.data[name]); | ||||
|       printf("'\n"); | ||||
|       break; | ||||
|     } | ||||
|        | ||||
|     case CODE_IMPORT_VARIABLE: | ||||
|     { | ||||
|       int variable = READ_SHORT(); | ||||
|       printf("%-16s %5d '", "IMPORT_VARIABLE", variable); | ||||
|       wrenDumpValue(fn->constants.data[variable]); | ||||
|       printf("'\n"); | ||||
|       break; | ||||
|     } | ||||
|        | ||||
|     case CODE_END: | ||||
|       printf("END\n"); | ||||
|       break; | ||||
| 
 | ||||
|     default: | ||||
|       printf("UKNOWN! [%d]\n", bytecode[i - 1]); | ||||
|       break; | ||||
|   } | ||||
| 
 | ||||
|   // Return how many bytes this instruction takes, or -1 if it's an END.
 | ||||
|   if (code == CODE_END) return -1; | ||||
|   return i - start; | ||||
| 
 | ||||
|   #undef READ_BYTE | ||||
|   #undef READ_SHORT | ||||
| } | ||||
| 
 | ||||
| int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i) | ||||
| { | ||||
|   return dumpInstruction(vm, fn, i, NULL); | ||||
| } | ||||
| 
 | ||||
| void wrenDumpCode(WrenVM* vm, ObjFn* fn) | ||||
| { | ||||
|   printf("%s: %s\n", | ||||
|          fn->module->name == NULL ? "<core>" : fn->module->name->value, | ||||
|          fn->debug->name); | ||||
| 
 | ||||
|   int i = 0; | ||||
|   int lastLine = -1; | ||||
|   for (;;) | ||||
|   { | ||||
|     int offset = dumpInstruction(vm, fn, i, &lastLine); | ||||
|     if (offset == -1) break; | ||||
|     i += offset; | ||||
|   } | ||||
| 
 | ||||
|   printf("\n"); | ||||
| } | ||||
| 
 | ||||
| void wrenDumpStack(ObjFiber* fiber) | ||||
| { | ||||
|   printf("(fiber %p) ", fiber); | ||||
|   for (Value* slot = fiber->stack; slot < fiber->stackTop; slot++) | ||||
|   { | ||||
|     wrenDumpValue(*slot); | ||||
|     printf(" | "); | ||||
|   } | ||||
|   printf("\n"); | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/logic/wren/vm/wren_debug.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/logic/wren/vm/wren_debug.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| #ifndef wren_debug_h | ||||
| #define wren_debug_h | ||||
| 
 | ||||
| #include "wren_value.h" | ||||
| #include "wren_vm.h" | ||||
| 
 | ||||
| // Prints the stack trace for the current fiber.
 | ||||
| //
 | ||||
| // Used when a fiber throws a runtime error which is not caught.
 | ||||
| void wrenDebugPrintStackTrace(WrenVM* vm); | ||||
| 
 | ||||
| // The "dump" functions are used for debugging Wren itself. Normal code paths
 | ||||
| // will not call them unless one of the various DEBUG_ flags is enabled.
 | ||||
| 
 | ||||
| // Prints a representation of [value] to stdout.
 | ||||
| void wrenDumpValue(Value value); | ||||
| 
 | ||||
| // Prints a representation of the bytecode for [fn] at instruction [i].
 | ||||
| int wrenDumpInstruction(WrenVM* vm, ObjFn* fn, int i); | ||||
| 
 | ||||
| // Prints the disassembled code for [fn] to stdout.
 | ||||
| void wrenDumpCode(WrenVM* vm, ObjFn* fn); | ||||
| 
 | ||||
| // Prints the contents of the current stack for [fiber] to stdout.
 | ||||
| void wrenDumpStack(ObjFiber* fiber); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										213
									
								
								src/logic/wren/vm/wren_opcodes.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/logic/wren/vm/wren_opcodes.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,213 @@ | |||
| // This defines the bytecode instructions used by the VM. It does so by invoking
 | ||||
| // an OPCODE() macro which is expected to be defined at the point that this is
 | ||||
| // included. (See: http://en.wikipedia.org/wiki/X_Macro for more.)
 | ||||
| //
 | ||||
| // The first argument is the name of the opcode. The second is its "stack
 | ||||
| // effect" -- the amount that the op code changes the size of the stack. A
 | ||||
| // stack effect of 1 means it pushes a value and the stack grows one larger.
 | ||||
| // -2 means it pops two values, etc.
 | ||||
| //
 | ||||
| // Note that the order of instructions here affects the order of the dispatch
 | ||||
| // table in the VM's interpreter loop. That in turn affects caching which
 | ||||
| // affects overall performance. Take care to run benchmarks if you change the
 | ||||
| // order here.
 | ||||
| 
 | ||||
| // Load the constant at index [arg].
 | ||||
| OPCODE(CONSTANT, 1) | ||||
| 
 | ||||
| // Push null onto the stack.
 | ||||
| OPCODE(NULL, 1) | ||||
| 
 | ||||
| // Push false onto the stack.
 | ||||
| OPCODE(FALSE, 1) | ||||
| 
 | ||||
| // Push true onto the stack.
 | ||||
| OPCODE(TRUE, 1) | ||||
| 
 | ||||
| // Pushes the value in the given local slot.
 | ||||
| OPCODE(LOAD_LOCAL_0, 1) | ||||
| OPCODE(LOAD_LOCAL_1, 1) | ||||
| OPCODE(LOAD_LOCAL_2, 1) | ||||
| OPCODE(LOAD_LOCAL_3, 1) | ||||
| OPCODE(LOAD_LOCAL_4, 1) | ||||
| OPCODE(LOAD_LOCAL_5, 1) | ||||
| OPCODE(LOAD_LOCAL_6, 1) | ||||
| OPCODE(LOAD_LOCAL_7, 1) | ||||
| OPCODE(LOAD_LOCAL_8, 1) | ||||
| 
 | ||||
| // Note: The compiler assumes the following _STORE instructions always
 | ||||
| // immediately follow their corresponding _LOAD ones.
 | ||||
| 
 | ||||
| // Pushes the value in local slot [arg].
 | ||||
| OPCODE(LOAD_LOCAL, 1) | ||||
| 
 | ||||
| // Stores the top of stack in local slot [arg]. Does not pop it.
 | ||||
| OPCODE(STORE_LOCAL, 0) | ||||
| 
 | ||||
| // Pushes the value in upvalue [arg].
 | ||||
| OPCODE(LOAD_UPVALUE, 1) | ||||
| 
 | ||||
| // Stores the top of stack in upvalue [arg]. Does not pop it.
 | ||||
| OPCODE(STORE_UPVALUE, 0) | ||||
| 
 | ||||
| // Pushes the value of the top-level variable in slot [arg].
 | ||||
| OPCODE(LOAD_MODULE_VAR, 1) | ||||
| 
 | ||||
| // Stores the top of stack in top-level variable slot [arg]. Does not pop it.
 | ||||
| OPCODE(STORE_MODULE_VAR, 0) | ||||
| 
 | ||||
| // Pushes the value of the field in slot [arg] of the receiver of the current
 | ||||
| // function. This is used for regular field accesses on "this" directly in
 | ||||
| // methods. This instruction is faster than the more general CODE_LOAD_FIELD
 | ||||
| // instruction.
 | ||||
| OPCODE(LOAD_FIELD_THIS, 1) | ||||
| 
 | ||||
| // Stores the top of the stack in field slot [arg] in the receiver of the
 | ||||
| // current value. Does not pop the value. This instruction is faster than the
 | ||||
| // more general CODE_LOAD_FIELD instruction.
 | ||||
| OPCODE(STORE_FIELD_THIS, 0) | ||||
| 
 | ||||
| // Pops an instance and pushes the value of the field in slot [arg] of it.
 | ||||
| OPCODE(LOAD_FIELD, 0) | ||||
| 
 | ||||
| // Pops an instance and stores the subsequent top of stack in field slot
 | ||||
| // [arg] in it. Does not pop the value.
 | ||||
| OPCODE(STORE_FIELD, -1) | ||||
| 
 | ||||
| // Pop and discard the top of stack.
 | ||||
| OPCODE(POP, -1) | ||||
| 
 | ||||
| // Invoke the method with symbol [arg]. The number indicates the number of
 | ||||
| // arguments (not including the receiver).
 | ||||
| OPCODE(CALL_0, 0) | ||||
| OPCODE(CALL_1, -1) | ||||
| OPCODE(CALL_2, -2) | ||||
| OPCODE(CALL_3, -3) | ||||
| OPCODE(CALL_4, -4) | ||||
| OPCODE(CALL_5, -5) | ||||
| OPCODE(CALL_6, -6) | ||||
| OPCODE(CALL_7, -7) | ||||
| OPCODE(CALL_8, -8) | ||||
| OPCODE(CALL_9, -9) | ||||
| OPCODE(CALL_10, -10) | ||||
| OPCODE(CALL_11, -11) | ||||
| OPCODE(CALL_12, -12) | ||||
| OPCODE(CALL_13, -13) | ||||
| OPCODE(CALL_14, -14) | ||||
| OPCODE(CALL_15, -15) | ||||
| OPCODE(CALL_16, -16) | ||||
| 
 | ||||
| // Invoke a superclass method with symbol [arg]. The number indicates the
 | ||||
| // number of arguments (not including the receiver).
 | ||||
| OPCODE(SUPER_0, 0) | ||||
| OPCODE(SUPER_1, -1) | ||||
| OPCODE(SUPER_2, -2) | ||||
| OPCODE(SUPER_3, -3) | ||||
| OPCODE(SUPER_4, -4) | ||||
| OPCODE(SUPER_5, -5) | ||||
| OPCODE(SUPER_6, -6) | ||||
| OPCODE(SUPER_7, -7) | ||||
| OPCODE(SUPER_8, -8) | ||||
| OPCODE(SUPER_9, -9) | ||||
| OPCODE(SUPER_10, -10) | ||||
| OPCODE(SUPER_11, -11) | ||||
| OPCODE(SUPER_12, -12) | ||||
| OPCODE(SUPER_13, -13) | ||||
| OPCODE(SUPER_14, -14) | ||||
| OPCODE(SUPER_15, -15) | ||||
| OPCODE(SUPER_16, -16) | ||||
| 
 | ||||
| // Jump the instruction pointer [arg] forward.
 | ||||
| OPCODE(JUMP, 0) | ||||
| 
 | ||||
| // Jump the instruction pointer [arg] backward.
 | ||||
| OPCODE(LOOP, 0) | ||||
| 
 | ||||
| // Pop and if not truthy then jump the instruction pointer [arg] forward.
 | ||||
| OPCODE(JUMP_IF, -1) | ||||
| 
 | ||||
| // If the top of the stack is false, jump [arg] forward. Otherwise, pop and
 | ||||
| // continue.
 | ||||
| OPCODE(AND, -1) | ||||
| 
 | ||||
| // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop
 | ||||
| // and continue.
 | ||||
| OPCODE(OR, -1) | ||||
| 
 | ||||
| // Close the upvalue for the local on the top of the stack, then pop it.
 | ||||
| OPCODE(CLOSE_UPVALUE, -1) | ||||
| 
 | ||||
| // Exit from the current function and return the value on the top of the
 | ||||
| // stack.
 | ||||
| OPCODE(RETURN, 0) | ||||
| 
 | ||||
| // Creates a closure for the function stored at [arg] in the constant table.
 | ||||
| //
 | ||||
| // Following the function argument is a number of arguments, two for each
 | ||||
| // upvalue. The first is true if the variable being captured is a local (as
 | ||||
| // opposed to an upvalue), and the second is the index of the local or
 | ||||
| // upvalue being captured.
 | ||||
| //
 | ||||
| // Pushes the created closure.
 | ||||
| OPCODE(CLOSURE, 1) | ||||
| 
 | ||||
| // Creates a new instance of a 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(CONSTRUCT, 0) | ||||
| 
 | ||||
| // 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, 0) | ||||
| 
 | ||||
| // Creates a class. Top of stack is the superclass. Below that is a string for
 | ||||
| // the name of the class. Byte [arg] is the number of fields in the class.
 | ||||
| OPCODE(CLASS, -1) | ||||
| 
 | ||||
| // Creates a foreign class. Top of stack is the superclass. Below that is a
 | ||||
| // string for the name of the class.
 | ||||
| OPCODE(FOREIGN_CLASS, -1) | ||||
| 
 | ||||
| // Define a method for symbol [arg]. The class receiving the method is popped
 | ||||
| // off the stack, then the function defining the body is popped.
 | ||||
| //
 | ||||
| // If a foreign method is being defined, the "function" will be a string
 | ||||
| // identifying the foreign method. Otherwise, it will be a function or
 | ||||
| // closure.
 | ||||
| OPCODE(METHOD_INSTANCE, -2) | ||||
| 
 | ||||
| // Define a method for symbol [arg]. The class whose metaclass will receive
 | ||||
| // the method is popped off the stack, then the function defining the body is
 | ||||
| // popped.
 | ||||
| //
 | ||||
| // If a foreign method is being defined, the "function" will be a string
 | ||||
| // identifying the foreign method. Otherwise, it will be a function or
 | ||||
| // closure.
 | ||||
| OPCODE(METHOD_STATIC, -2) | ||||
| 
 | ||||
| // This is executed at the end of the module's body. Pushes NULL onto the stack
 | ||||
| // as the "return value" of the import statement and stores the module as the
 | ||||
| // most recently imported one.
 | ||||
| OPCODE(END_MODULE, 1) | ||||
| 
 | ||||
| // Import a module whose name is the string stored at [arg] in the constant
 | ||||
| // table.
 | ||||
| //
 | ||||
| // Pushes null onto the stack so that the fiber for the imported module can
 | ||||
| // replace that with a dummy value when it returns. (Fibers always return a
 | ||||
| // value when resuming a caller.)
 | ||||
| OPCODE(IMPORT_MODULE, 1) | ||||
| 
 | ||||
| // Import a variable from the most recently imported module. The name of the
 | ||||
| // variable to import is at [arg] in the constant table. Pushes the loaded
 | ||||
| // variable's value.
 | ||||
| OPCODE(IMPORT_VARIABLE, 1) | ||||
| 
 | ||||
| // This pseudo-instruction indicates the end of the bytecode. It should
 | ||||
| // always be preceded by a `CODE_RETURN`, so is never actually executed.
 | ||||
| OPCODE(END, 0) | ||||
							
								
								
									
										125
									
								
								src/logic/wren/vm/wren_primitive.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/logic/wren/vm/wren_primitive.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,125 @@ | |||
| #include "wren_primitive.h" | ||||
| 
 | ||||
| #include <math.h> | ||||
| 
 | ||||
| // Validates that [value] is an integer within `[0, count)`. Also allows
 | ||||
| // negative indices which map backwards from the end. Returns the valid positive
 | ||||
| // index value. If invalid, reports an error and returns `UINT32_MAX`.
 | ||||
| static uint32_t validateIndexValue(WrenVM* vm, uint32_t count, double value, | ||||
|                                    const char* argName) | ||||
| { | ||||
|   if (!validateIntValue(vm, value, argName)) return UINT32_MAX; | ||||
|    | ||||
|   // Negative indices count from the end.
 | ||||
|   if (value < 0) value = count + value; | ||||
|    | ||||
|   // Check bounds.
 | ||||
|   if (value >= 0 && value < count) return (uint32_t)value; | ||||
|    | ||||
|   vm->fiber->error = wrenStringFormat(vm, "$ out of bounds.", argName); | ||||
|   return UINT32_MAX; | ||||
| } | ||||
| 
 | ||||
| bool validateFn(WrenVM* vm, Value arg, const char* argName) | ||||
| { | ||||
|   if (IS_CLOSURE(arg)) return true; | ||||
| 
 | ||||
|   vm->fiber->error = wrenStringFormat(vm, "$ must be a function.", argName); | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| bool validateNum(WrenVM* vm, Value arg, const char* argName) | ||||
| { | ||||
|   if (IS_NUM(arg)) return true; | ||||
|   RETURN_ERROR_FMT("$ must be a number.", argName); | ||||
| } | ||||
| 
 | ||||
| bool validateIntValue(WrenVM* vm, double value, const char* argName) | ||||
| { | ||||
|   if (trunc(value) == value) return true; | ||||
|   RETURN_ERROR_FMT("$ must be an integer.", argName); | ||||
| } | ||||
| 
 | ||||
| bool validateInt(WrenVM* vm, Value arg, const char* argName) | ||||
| { | ||||
|   // Make sure it's a number first.
 | ||||
|   if (!validateNum(vm, arg, argName)) return false; | ||||
|   return validateIntValue(vm, AS_NUM(arg), argName); | ||||
| } | ||||
| 
 | ||||
| bool validateKey(WrenVM* vm, Value arg) | ||||
| { | ||||
|   if (IS_BOOL(arg) || IS_CLASS(arg) || IS_NULL(arg) || | ||||
|       IS_NUM(arg) || IS_RANGE(arg) || IS_STRING(arg)) | ||||
|   { | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   RETURN_ERROR("Key must be a value type."); | ||||
| } | ||||
| 
 | ||||
| uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count, | ||||
|                        const char* argName) | ||||
| { | ||||
|   if (!validateNum(vm, arg, argName)) return UINT32_MAX; | ||||
|   return validateIndexValue(vm, count, AS_NUM(arg), argName); | ||||
| } | ||||
| 
 | ||||
| bool validateString(WrenVM* vm, Value arg, const char* argName) | ||||
| { | ||||
|   if (IS_STRING(arg)) return true; | ||||
|   RETURN_ERROR_FMT("$ must be a string.", argName); | ||||
| } | ||||
| 
 | ||||
| uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, | ||||
|                         int* step) | ||||
| { | ||||
|   *step = 0; | ||||
| 
 | ||||
|   // Edge case: an empty range is allowed at the end of a sequence. This way,
 | ||||
|   // list[0..-1] and list[0...list.count] can be used to copy a list even when
 | ||||
|   // empty.
 | ||||
|   if (range->from == *length && | ||||
|       range->to == (range->isInclusive ? -1.0 : (double)*length)) | ||||
|   { | ||||
|     *length = 0; | ||||
|     return 0; | ||||
|   } | ||||
| 
 | ||||
|   uint32_t from = validateIndexValue(vm, *length, range->from, "Range start"); | ||||
|   if (from == UINT32_MAX) return UINT32_MAX; | ||||
| 
 | ||||
|   // Bounds check the end manually to handle exclusive ranges.
 | ||||
|   double value = range->to; | ||||
|   if (!validateIntValue(vm, value, "Range end")) return UINT32_MAX; | ||||
| 
 | ||||
|   // Negative indices count from the end.
 | ||||
|   if (value < 0) value = *length + value; | ||||
| 
 | ||||
|   // Convert the exclusive range to an inclusive one.
 | ||||
|   if (!range->isInclusive) | ||||
|   { | ||||
|     // An exclusive range with the same start and end points is empty.
 | ||||
|     if (value == from) | ||||
|     { | ||||
|       *length = 0; | ||||
|       return from; | ||||
|     } | ||||
| 
 | ||||
|     // Shift the endpoint to make it inclusive, handling both increasing and
 | ||||
|     // decreasing ranges.
 | ||||
|     value += value >= from ? -1 : 1; | ||||
|   } | ||||
| 
 | ||||
|   // Check bounds.
 | ||||
|   if (value < 0 || value >= *length) | ||||
|   { | ||||
|     vm->fiber->error = CONST_STRING(vm, "Range end out of bounds."); | ||||
|     return UINT32_MAX; | ||||
|   } | ||||
| 
 | ||||
|   uint32_t to = (uint32_t)value; | ||||
|   *length = abs((int)(from - to)) + 1; | ||||
|   *step = from < to ? 1 : -1; | ||||
|   return from; | ||||
| } | ||||
							
								
								
									
										88
									
								
								src/logic/wren/vm/wren_primitive.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/logic/wren/vm/wren_primitive.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | |||
| #ifndef wren_primitive_h | ||||
| #define wren_primitive_h | ||||
| 
 | ||||
| #include "wren_vm.h" | ||||
| 
 | ||||
| // Binds a primitive method named [name] (in Wren) implemented using C function
 | ||||
| // [fn] to `ObjClass` [cls].
 | ||||
| #define PRIMITIVE(cls, name, function) \ | ||||
|     { \ | ||||
|       int symbol = wrenSymbolTableEnsure(vm, \ | ||||
|           &vm->methodNames, name, strlen(name)); \ | ||||
|       Method method; \ | ||||
|       method.type = METHOD_PRIMITIVE; \ | ||||
|       method.as.primitive = prim_##function; \ | ||||
|       wrenBindMethod(vm, cls, symbol, method); \ | ||||
|     } | ||||
| 
 | ||||
| // Defines a primitive method whose C function name is [name]. This abstracts
 | ||||
| // the actual type signature of a primitive function and makes it clear which C
 | ||||
| // functions are invoked as primitives.
 | ||||
| #define DEF_PRIMITIVE(name) \ | ||||
|     static bool prim_##name(WrenVM* vm, Value* args) | ||||
| 
 | ||||
| #define RETURN_VAL(value)   do { args[0] = value; return true; } while (0) | ||||
| 
 | ||||
| #define RETURN_OBJ(obj)     RETURN_VAL(OBJ_VAL(obj)) | ||||
| #define RETURN_BOOL(value)  RETURN_VAL(BOOL_VAL(value)) | ||||
| #define RETURN_FALSE        RETURN_VAL(FALSE_VAL) | ||||
| #define RETURN_NULL         RETURN_VAL(NULL_VAL) | ||||
| #define RETURN_NUM(value)   RETURN_VAL(NUM_VAL(value)) | ||||
| #define RETURN_TRUE         RETURN_VAL(TRUE_VAL) | ||||
| 
 | ||||
| #define RETURN_ERROR(msg) \ | ||||
|     do { \ | ||||
|       vm->fiber->error = wrenNewStringLength(vm, msg, sizeof(msg) - 1); \ | ||||
|       return false; \ | ||||
|     } while (0); | ||||
| 
 | ||||
| #define RETURN_ERROR_FMT(msg, arg) \ | ||||
|     do { \ | ||||
|       vm->fiber->error = wrenStringFormat(vm, msg, arg); \ | ||||
|       return false; \ | ||||
|     } while (0); | ||||
| 
 | ||||
| // Validates that the given [arg] is a function. Returns true if it is. If not,
 | ||||
| // reports an error and returns false.
 | ||||
| bool validateFn(WrenVM* vm, Value arg, const char* argName); | ||||
| 
 | ||||
| // Validates that the given [arg] is a Num. Returns true if it is. If not,
 | ||||
| // reports an error and returns false.
 | ||||
| bool validateNum(WrenVM* vm, Value arg, const char* argName); | ||||
| 
 | ||||
| // Validates that [value] is an integer. Returns true if it is. If not, reports
 | ||||
| // an error and returns false.
 | ||||
| bool validateIntValue(WrenVM* vm, double value, const char* argName); | ||||
| 
 | ||||
| // Validates that the given [arg] is an integer. Returns true if it is. If not,
 | ||||
| // reports an error and returns false.
 | ||||
| bool validateInt(WrenVM* vm, Value arg, const char* argName); | ||||
| 
 | ||||
| // Validates that [arg] is a valid object for use as a map key. Returns true if
 | ||||
| // it is. If not, reports an error and returns false.
 | ||||
| bool validateKey(WrenVM* vm, Value arg); | ||||
| 
 | ||||
| // Validates that the argument at [argIndex] is an integer within `[0, count)`.
 | ||||
| // Also allows negative indices which map backwards from the end. Returns the
 | ||||
| // valid positive index value. If invalid, reports an error and returns
 | ||||
| // `UINT32_MAX`.
 | ||||
| uint32_t validateIndex(WrenVM* vm, Value arg, uint32_t count, | ||||
|                        const char* argName); | ||||
| 
 | ||||
| // Validates that the given [arg] is a String. Returns true if it is. If not,
 | ||||
| // reports an error and returns false.
 | ||||
| bool validateString(WrenVM* vm, Value arg, const char* argName); | ||||
| 
 | ||||
| // Given a [range] and the [length] of the object being operated on, determines
 | ||||
| // the series of elements that should be chosen from the underlying object.
 | ||||
| // Handles ranges that count backwards from the end as well as negative ranges.
 | ||||
| //
 | ||||
| // Returns the index from which the range should start or `UINT32_MAX` if the
 | ||||
| // range is invalid. After calling, [length] will be updated with the number of
 | ||||
| // elements in the resulting sequence. [step] will be direction that the range
 | ||||
| // is going: `1` if the range is increasing from the start index or `-1` if the
 | ||||
| // range is decreasing.
 | ||||
| uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length, | ||||
|                         int* step); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										196
									
								
								src/logic/wren/vm/wren_utils.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/logic/wren/vm/wren_utils.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,196 @@ | |||
| #include <string.h> | ||||
| 
 | ||||
| #include "wren_utils.h" | ||||
| #include "wren_vm.h" | ||||
| 
 | ||||
| DEFINE_BUFFER(Byte, uint8_t); | ||||
| DEFINE_BUFFER(Int, int); | ||||
| DEFINE_BUFFER(String, ObjString*); | ||||
| 
 | ||||
| void wrenSymbolTableInit(SymbolTable* symbols) | ||||
| { | ||||
|   wrenStringBufferInit(symbols); | ||||
| } | ||||
| 
 | ||||
| void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols) | ||||
| { | ||||
|   wrenStringBufferClear(vm, symbols); | ||||
| } | ||||
| 
 | ||||
| int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, | ||||
|                        const char* name, size_t length) | ||||
| { | ||||
|   ObjString* symbol = AS_STRING(wrenNewStringLength(vm, name, length)); | ||||
|    | ||||
|   wrenPushRoot(vm, &symbol->obj); | ||||
|   wrenStringBufferWrite(vm, symbols, symbol); | ||||
|   wrenPopRoot(vm); | ||||
|    | ||||
|   return symbols->count - 1; | ||||
| } | ||||
| 
 | ||||
| int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, | ||||
|                           const char* name, size_t length) | ||||
| { | ||||
|   // See if the symbol is already defined.
 | ||||
|   int existing = wrenSymbolTableFind(symbols, name, length); | ||||
|   if (existing != -1) return existing; | ||||
| 
 | ||||
|   // New symbol, so add it.
 | ||||
|   return wrenSymbolTableAdd(vm, symbols, name, length); | ||||
| } | ||||
| 
 | ||||
| int wrenSymbolTableFind(const SymbolTable* symbols, | ||||
|                         const char* name, size_t length) | ||||
| { | ||||
|   // See if the symbol is already defined.
 | ||||
|   // TODO: O(n). Do something better.
 | ||||
|   for (int i = 0; i < symbols->count; i++) | ||||
|   { | ||||
|     if (wrenStringEqualsCString(symbols->data[i], name, length)) return i; | ||||
|   } | ||||
| 
 | ||||
|   return -1; | ||||
| } | ||||
| 
 | ||||
| void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable) | ||||
| { | ||||
|   for (int i = 0; i < symbolTable->count; i++) | ||||
|   { | ||||
|     wrenGrayObj(vm, &symbolTable->data[i]->obj); | ||||
|   } | ||||
|    | ||||
|   // Keep track of how much memory is still in use.
 | ||||
|   vm->bytesAllocated += symbolTable->capacity * sizeof(*symbolTable->data); | ||||
| } | ||||
| 
 | ||||
| int wrenUtf8EncodeNumBytes(int value) | ||||
| { | ||||
|   ASSERT(value >= 0, "Cannot encode a negative value."); | ||||
|    | ||||
|   if (value <= 0x7f) return 1; | ||||
|   if (value <= 0x7ff) return 2; | ||||
|   if (value <= 0xffff) return 3; | ||||
|   if (value <= 0x10ffff) return 4; | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| int wrenUtf8Encode(int value, uint8_t* bytes) | ||||
| { | ||||
|   if (value <= 0x7f) | ||||
|   { | ||||
|     // Single byte (i.e. fits in ASCII).
 | ||||
|     *bytes = value & 0x7f; | ||||
|     return 1; | ||||
|   } | ||||
|   else if (value <= 0x7ff) | ||||
|   { | ||||
|     // Two byte sequence: 110xxxxx 10xxxxxx.
 | ||||
|     *bytes = 0xc0 | ((value & 0x7c0) >> 6); | ||||
|     bytes++; | ||||
|     *bytes = 0x80 | (value & 0x3f); | ||||
|     return 2; | ||||
|   } | ||||
|   else if (value <= 0xffff) | ||||
|   { | ||||
|     // Three byte sequence: 1110xxxx 10xxxxxx 10xxxxxx.
 | ||||
|     *bytes = 0xe0 | ((value & 0xf000) >> 12); | ||||
|     bytes++; | ||||
|     *bytes = 0x80 | ((value & 0xfc0) >> 6); | ||||
|     bytes++; | ||||
|     *bytes = 0x80 | (value & 0x3f); | ||||
|     return 3; | ||||
|   } | ||||
|   else if (value <= 0x10ffff) | ||||
|   { | ||||
|     // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
 | ||||
|     *bytes = 0xf0 | ((value & 0x1c0000) >> 18); | ||||
|     bytes++; | ||||
|     *bytes = 0x80 | ((value & 0x3f000) >> 12); | ||||
|     bytes++; | ||||
|     *bytes = 0x80 | ((value & 0xfc0) >> 6); | ||||
|     bytes++; | ||||
|     *bytes = 0x80 | (value & 0x3f); | ||||
|     return 4; | ||||
|   } | ||||
| 
 | ||||
|   // Invalid Unicode value. See: http://tools.ietf.org/html/rfc3629
 | ||||
|   UNREACHABLE(); | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| int wrenUtf8Decode(const uint8_t* bytes, uint32_t length) | ||||
| { | ||||
|   // Single byte (i.e. fits in ASCII).
 | ||||
|   if (*bytes <= 0x7f) return *bytes; | ||||
| 
 | ||||
|   int value; | ||||
|   uint32_t remainingBytes; | ||||
|   if ((*bytes & 0xe0) == 0xc0) | ||||
|   { | ||||
|     // Two byte sequence: 110xxxxx 10xxxxxx.
 | ||||
|     value = *bytes & 0x1f; | ||||
|     remainingBytes = 1; | ||||
|   } | ||||
|   else if ((*bytes & 0xf0) == 0xe0) | ||||
|   { | ||||
|     // Three byte sequence: 1110xxxx	 10xxxxxx 10xxxxxx.
 | ||||
|     value = *bytes & 0x0f; | ||||
|     remainingBytes = 2; | ||||
|   } | ||||
|   else if ((*bytes & 0xf8) == 0xf0) | ||||
|   { | ||||
|     // Four byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
 | ||||
|     value = *bytes & 0x07; | ||||
|     remainingBytes = 3; | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     // Invalid UTF-8 sequence.
 | ||||
|     return -1; | ||||
|   } | ||||
| 
 | ||||
|   // Don't read past the end of the buffer on truncated UTF-8.
 | ||||
|   if (remainingBytes > length - 1) return -1; | ||||
| 
 | ||||
|   while (remainingBytes > 0) | ||||
|   { | ||||
|     bytes++; | ||||
|     remainingBytes--; | ||||
| 
 | ||||
|     // Remaining bytes must be of form 10xxxxxx.
 | ||||
|     if ((*bytes & 0xc0) != 0x80) return -1; | ||||
| 
 | ||||
|     value = value << 6 | (*bytes & 0x3f); | ||||
|   } | ||||
| 
 | ||||
|   return value; | ||||
| } | ||||
| 
 | ||||
| int wrenUtf8DecodeNumBytes(uint8_t byte) | ||||
| { | ||||
|   // If the byte starts with 10xxxxx, it's the middle of a UTF-8 sequence, so
 | ||||
|   // don't count it at all.
 | ||||
|   if ((byte & 0xc0) == 0x80) return 0; | ||||
|    | ||||
|   // The first byte's high bits tell us how many bytes are in the UTF-8
 | ||||
|   // sequence.
 | ||||
|   if ((byte & 0xf8) == 0xf0) return 4; | ||||
|   if ((byte & 0xf0) == 0xe0) return 3; | ||||
|   if ((byte & 0xe0) == 0xc0) return 2; | ||||
|   return 1; | ||||
| } | ||||
| 
 | ||||
| // From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float
 | ||||
| int wrenPowerOf2Ceil(int n) | ||||
| { | ||||
|   n--; | ||||
|   n |= n >> 1; | ||||
|   n |= n >> 2; | ||||
|   n |= n >> 4; | ||||
|   n |= n >> 8; | ||||
|   n |= n >> 16; | ||||
|   n++; | ||||
|    | ||||
|   return n; | ||||
| } | ||||
							
								
								
									
										121
									
								
								src/logic/wren/vm/wren_utils.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/logic/wren/vm/wren_utils.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,121 @@ | |||
| #ifndef wren_utils_h | ||||
| #define wren_utils_h | ||||
| 
 | ||||
| #include "wren.h" | ||||
| #include "wren_common.h" | ||||
| 
 | ||||
| // Reusable data structures and other utility functions.
 | ||||
| 
 | ||||
| // Forward declare this here to break a cycle between wren_utils.h and
 | ||||
| // wren_value.h.
 | ||||
| typedef struct sObjString ObjString; | ||||
| 
 | ||||
| // We need buffers of a few different types. To avoid lots of casting between
 | ||||
| // void* and back, we'll use the preprocessor as a poor man's generics and let
 | ||||
| // it generate a few type-specific ones.
 | ||||
| #define DECLARE_BUFFER(name, type) \ | ||||
|     typedef struct \ | ||||
|     { \ | ||||
|       type* data; \ | ||||
|       int count; \ | ||||
|       int capacity; \ | ||||
|     } name##Buffer; \ | ||||
|     void wren##name##BufferInit(name##Buffer* buffer); \ | ||||
|     void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer); \ | ||||
|     void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \ | ||||
|                                 int count); \ | ||||
|     void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) | ||||
| 
 | ||||
| // This should be used once for each type instantiation, somewhere in a .c file.
 | ||||
| #define DEFINE_BUFFER(name, type) \ | ||||
|     void wren##name##BufferInit(name##Buffer* buffer) \ | ||||
|     { \ | ||||
|       buffer->data = NULL; \ | ||||
|       buffer->capacity = 0; \ | ||||
|       buffer->count = 0; \ | ||||
|     } \ | ||||
|     \ | ||||
|     void wren##name##BufferClear(WrenVM* vm, name##Buffer* buffer) \ | ||||
|     { \ | ||||
|       wrenReallocate(vm, buffer->data, 0, 0); \ | ||||
|       wren##name##BufferInit(buffer); \ | ||||
|     } \ | ||||
|     \ | ||||
|     void wren##name##BufferFill(WrenVM* vm, name##Buffer* buffer, type data, \ | ||||
|                                 int count) \ | ||||
|     { \ | ||||
|       if (buffer->capacity < buffer->count + count) \ | ||||
|       { \ | ||||
|         int capacity = wrenPowerOf2Ceil(buffer->count + count); \ | ||||
|         buffer->data = (type*)wrenReallocate(vm, buffer->data, \ | ||||
|             buffer->capacity * sizeof(type), capacity * sizeof(type)); \ | ||||
|         buffer->capacity = capacity; \ | ||||
|       } \ | ||||
|       \ | ||||
|       for (int i = 0; i < count; i++) \ | ||||
|       { \ | ||||
|         buffer->data[buffer->count++] = data; \ | ||||
|       } \ | ||||
|     } \ | ||||
|     \ | ||||
|     void wren##name##BufferWrite(WrenVM* vm, name##Buffer* buffer, type data) \ | ||||
|     { \ | ||||
|       wren##name##BufferFill(vm, buffer, data, 1); \ | ||||
|     } | ||||
| 
 | ||||
| DECLARE_BUFFER(Byte, uint8_t); | ||||
| DECLARE_BUFFER(Int, int); | ||||
| DECLARE_BUFFER(String, ObjString*); | ||||
| 
 | ||||
| // TODO: Change this to use a map.
 | ||||
| typedef StringBuffer SymbolTable; | ||||
| 
 | ||||
| // Initializes the symbol table.
 | ||||
| void wrenSymbolTableInit(SymbolTable* symbols); | ||||
| 
 | ||||
| // Frees all dynamically allocated memory used by the symbol table, but not the
 | ||||
| // SymbolTable itself.
 | ||||
| void wrenSymbolTableClear(WrenVM* vm, SymbolTable* symbols); | ||||
| 
 | ||||
| // Adds name to the symbol table. Returns the index of it in the table.
 | ||||
| int wrenSymbolTableAdd(WrenVM* vm, SymbolTable* symbols, | ||||
|                        const char* name, size_t length); | ||||
| 
 | ||||
| // Adds name to the symbol table. Returns the index of it in the table. Will
 | ||||
| // use an existing symbol if already present.
 | ||||
| int wrenSymbolTableEnsure(WrenVM* vm, SymbolTable* symbols, | ||||
|                           const char* name, size_t length); | ||||
| 
 | ||||
| // Looks up name in the symbol table. Returns its index if found or -1 if not.
 | ||||
| int wrenSymbolTableFind(const SymbolTable* symbols, | ||||
|                         const char* name, size_t length); | ||||
| 
 | ||||
| void wrenBlackenSymbolTable(WrenVM* vm, SymbolTable* symbolTable); | ||||
| 
 | ||||
| // Returns the number of bytes needed to encode [value] in UTF-8.
 | ||||
| //
 | ||||
| // Returns 0 if [value] is too large to encode.
 | ||||
| int wrenUtf8EncodeNumBytes(int value); | ||||
| 
 | ||||
| // Encodes value as a series of bytes in [bytes], which is assumed to be large
 | ||||
| // enough to hold the encoded result.
 | ||||
| //
 | ||||
| // Returns the number of written bytes.
 | ||||
| int wrenUtf8Encode(int value, uint8_t* bytes); | ||||
| 
 | ||||
| // Decodes the UTF-8 sequence starting at [bytes] (which has max [length]),
 | ||||
| // returning the code point.
 | ||||
| //
 | ||||
| // Returns -1 if the bytes are not a valid UTF-8 sequence.
 | ||||
| int wrenUtf8Decode(const uint8_t* bytes, uint32_t length); | ||||
| 
 | ||||
| // Returns the number of bytes in the UTF-8 sequence starting with [byte].
 | ||||
| //
 | ||||
| // If the character at that index is not the beginning of a UTF-8 sequence,
 | ||||
| // returns 0.
 | ||||
| int wrenUtf8DecodeNumBytes(uint8_t byte); | ||||
| 
 | ||||
| // Returns the smallest power of two that is equal to or greater than [n].
 | ||||
| int wrenPowerOf2Ceil(int n); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										1315
									
								
								src/logic/wren/vm/wren_value.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1315
									
								
								src/logic/wren/vm/wren_value.c
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										876
									
								
								src/logic/wren/vm/wren_value.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										876
									
								
								src/logic/wren/vm/wren_value.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,876 @@ | |||
| #ifndef wren_value_h | ||||
| #define wren_value_h | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "wren_common.h" | ||||
| #include "wren_utils.h" | ||||
| 
 | ||||
| // This defines the built-in types and their core representations in memory.
 | ||||
| // Since Wren is dynamically typed, any variable can hold a value of any type,
 | ||||
| // and the type can change at runtime. Implementing this efficiently is
 | ||||
| // critical for performance.
 | ||||
| //
 | ||||
| // The main type exposed by this is [Value]. A C variable of that type is a
 | ||||
| // storage location that can hold any Wren value. The stack, module variables,
 | ||||
| // and instance fields are all implemented in C as variables of type Value.
 | ||||
| //
 | ||||
| // The built-in types for booleans, numbers, and null are unboxed: their value
 | ||||
| // is stored directly in the Value, and copying a Value copies the value. Other
 | ||||
| // types--classes, instances of classes, functions, lists, and strings--are all
 | ||||
| // reference types. They are stored on the heap and the Value just stores a
 | ||||
| // pointer to it. Copying the Value copies a reference to the same object. The
 | ||||
| // Wren implementation calls these "Obj", or objects, though to a user, all
 | ||||
| // values are objects.
 | ||||
| //
 | ||||
| // There is also a special singleton value "undefined". It is used internally
 | ||||
| // but never appears as a real value to a user. It has two uses:
 | ||||
| //
 | ||||
| // - It is used to identify module variables that have been implicitly declared
 | ||||
| //   by use in a forward reference but not yet explicitly declared. These only
 | ||||
| //   exist during compilation and do not appear at runtime.
 | ||||
| //
 | ||||
| // - It is used to represent unused map entries in an ObjMap.
 | ||||
| //
 | ||||
| // There are two supported Value representations. The main one uses a technique
 | ||||
| // called "NaN tagging" (explained in detail below) to store a number, any of
 | ||||
| // the value types, or a pointer, all inside one double-precision floating
 | ||||
| // point number. A larger, slower, Value type that uses a struct to store these
 | ||||
| // is also supported, and is useful for debugging the VM.
 | ||||
| //
 | ||||
| // The representation is controlled by the `WREN_NAN_TAGGING` define. If that's
 | ||||
| // defined, Nan tagging is used.
 | ||||
| 
 | ||||
| // These macros cast a Value to one of the specific object types. These do *not*
 | ||||
| // perform any validation, so must only be used after the Value has been
 | ||||
| // ensured to be the right type.
 | ||||
| #define AS_CLASS(value)     ((ObjClass*)AS_OBJ(value))          // ObjClass*
 | ||||
| #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*
 | ||||
| #define AS_MODULE(value)    ((ObjModule*)AS_OBJ(value))         // ObjModule*
 | ||||
| #define AS_NUM(value)       (wrenValueToNum(value))             // double
 | ||||
| #define AS_RANGE(v)         ((ObjRange*)AS_OBJ(v))              // ObjRange*
 | ||||
| #define AS_STRING(v)        ((ObjString*)AS_OBJ(v))             // ObjString*
 | ||||
| #define AS_CSTRING(v)       (AS_STRING(v)->value)               // const char*
 | ||||
| 
 | ||||
| // These macros promote a primitive C value to a full Wren Value. There are
 | ||||
| // more defined below that are specific to the Nan tagged or other
 | ||||
| // representation.
 | ||||
| #define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL : FALSE_VAL)    // boolean
 | ||||
| #define NUM_VAL(num) (wrenNumToValue(num))                      // double
 | ||||
| #define OBJ_VAL(obj) (wrenObjectToValue((Obj*)(obj)))           // Any Obj___*
 | ||||
| 
 | ||||
| // These perform type tests on a Value, returning `true` if the Value is of the
 | ||||
| // given type.
 | ||||
| #define IS_BOOL(value) (wrenIsBool(value))                      // Bool
 | ||||
| #define IS_CLASS(value) (wrenIsObjType(value, OBJ_CLASS))       // ObjClass
 | ||||
| #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_LIST(value) (wrenIsObjType(value, OBJ_LIST))         // ObjList
 | ||||
| #define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE))       // ObjRange
 | ||||
| #define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING))     // ObjString
 | ||||
| 
 | ||||
| // Creates a new string object from [text], which should be a bare C string
 | ||||
| // literal. This determines the length of the string automatically at compile
 | ||||
| // time based on the size of the character array (-1 for the terminating '\0').
 | ||||
| #define CONST_STRING(vm, text) wrenNewStringLength((vm), (text), sizeof(text) - 1) | ||||
| 
 | ||||
| // Identifies which specific type a heap-allocated object is.
 | ||||
| typedef enum { | ||||
|   OBJ_CLASS, | ||||
|   OBJ_CLOSURE, | ||||
|   OBJ_FIBER, | ||||
|   OBJ_FN, | ||||
|   OBJ_FOREIGN, | ||||
|   OBJ_INSTANCE, | ||||
|   OBJ_LIST, | ||||
|   OBJ_MAP, | ||||
|   OBJ_MODULE, | ||||
|   OBJ_RANGE, | ||||
|   OBJ_STRING, | ||||
|   OBJ_UPVALUE | ||||
| } ObjType; | ||||
| 
 | ||||
| typedef struct sObjClass ObjClass; | ||||
| 
 | ||||
| // Base struct for all heap-allocated objects.
 | ||||
| typedef struct sObj Obj; | ||||
| struct sObj | ||||
| { | ||||
|   ObjType type; | ||||
|   bool isDark; | ||||
| 
 | ||||
|   // The object's class.
 | ||||
|   ObjClass* classObj; | ||||
| 
 | ||||
|   // The next object in the linked list of all currently allocated objects.
 | ||||
|   struct sObj* next; | ||||
| }; | ||||
| 
 | ||||
| #if WREN_NAN_TAGGING | ||||
| 
 | ||||
| typedef uint64_t Value; | ||||
| 
 | ||||
| #else | ||||
| 
 | ||||
| typedef enum | ||||
| { | ||||
|   VAL_FALSE, | ||||
|   VAL_NULL, | ||||
|   VAL_NUM, | ||||
|   VAL_TRUE, | ||||
|   VAL_UNDEFINED, | ||||
|   VAL_OBJ | ||||
| } ValueType; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   ValueType type; | ||||
|   union | ||||
|   { | ||||
|     double num; | ||||
|     Obj* obj; | ||||
|   } as; | ||||
| } Value; | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| DECLARE_BUFFER(Value, Value); | ||||
| 
 | ||||
| // A heap-allocated string object.
 | ||||
| struct sObjString | ||||
| { | ||||
|   Obj obj; | ||||
| 
 | ||||
|   // Number of bytes in the string, not including the null terminator.
 | ||||
|   uint32_t length; | ||||
| 
 | ||||
|   // The hash value of the string's contents.
 | ||||
|   uint32_t hash; | ||||
| 
 | ||||
|   // Inline array of the string's bytes followed by a null terminator.
 | ||||
|   char value[FLEXIBLE_ARRAY]; | ||||
| }; | ||||
| 
 | ||||
| // The dynamically allocated data structure for a variable that has been used
 | ||||
| // by a closure. Whenever a function accesses a variable declared in an
 | ||||
| // enclosing function, it will get to it through this.
 | ||||
| //
 | ||||
| // An upvalue can be either "closed" or "open". An open upvalue points directly
 | ||||
| // to a [Value] that is still stored on the fiber's stack because the local
 | ||||
| // variable is still in scope in the function where it's declared.
 | ||||
| //
 | ||||
| // When that local variable goes out of scope, the upvalue pointing to it will
 | ||||
| // be closed. When that happens, the value gets copied off the stack into the
 | ||||
| // upvalue itself. That way, it can have a longer lifetime than the stack
 | ||||
| // variable.
 | ||||
| typedef struct sObjUpvalue | ||||
| { | ||||
|   // The object header. Note that upvalues have this because they are garbage
 | ||||
|   // collected, but they are not first class Wren objects.
 | ||||
|   Obj obj; | ||||
| 
 | ||||
|   // Pointer to the variable this upvalue is referencing.
 | ||||
|   Value* value; | ||||
| 
 | ||||
|   // If the upvalue is closed (i.e. the local variable it was pointing too has
 | ||||
|   // been popped off the stack) then the closed-over value will be hoisted out
 | ||||
|   // of the stack into here. [value] will then be changed to point to this.
 | ||||
|   Value closed; | ||||
| 
 | ||||
|   // Open upvalues are stored in a linked list by the fiber. This points to the
 | ||||
|   // next upvalue in that list.
 | ||||
|   struct sObjUpvalue* next; | ||||
| } ObjUpvalue; | ||||
| 
 | ||||
| // The type of a primitive function.
 | ||||
| //
 | ||||
| // Primitives are similiar to foreign functions, but have more direct access to
 | ||||
| // VM internals. It is passed the arguments in [args]. If it returns a value,
 | ||||
| // it places it in `args[0]` and returns `true`. If it causes a runtime error
 | ||||
| // or modifies the running fiber, it returns `false`.
 | ||||
| typedef bool (*Primitive)(WrenVM* vm, Value* args); | ||||
| 
 | ||||
| // TODO: See if it's actually a perf improvement to have this in a separate
 | ||||
| // struct instead of in ObjFn.
 | ||||
| // Stores debugging information for a function used for things like stack
 | ||||
| // traces.
 | ||||
| typedef struct | ||||
| { | ||||
|   // The name of the function. Heap allocated and owned by the FnDebug.
 | ||||
|   char* name; | ||||
| 
 | ||||
|   // An array of line numbers. There is one element in this array for each
 | ||||
|   // bytecode in the function's bytecode array. The value of that element is
 | ||||
|   // the line in the source code that generated that instruction.
 | ||||
|   IntBuffer sourceLines; | ||||
| } FnDebug; | ||||
| 
 | ||||
| // A loaded module and the top-level variables it defines.
 | ||||
| //
 | ||||
| // While this is an Obj and is managed by the GC, it never appears as a
 | ||||
| // first-class object in Wren.
 | ||||
| typedef struct | ||||
| { | ||||
|   Obj obj; | ||||
| 
 | ||||
|   // The currently defined top-level variables.
 | ||||
|   ValueBuffer variables; | ||||
| 
 | ||||
|   // Symbol table for the names of all module variables. Indexes here directly
 | ||||
|   // correspond to entries in [variables].
 | ||||
|   SymbolTable variableNames; | ||||
| 
 | ||||
|   // The name of the module.
 | ||||
|   ObjString* name; | ||||
| } ObjModule; | ||||
| 
 | ||||
| // A function object. It wraps and owns the bytecode and other debug information
 | ||||
| // for a callable chunk of code.
 | ||||
| //
 | ||||
| // Function objects are not passed around and invoked directly. Instead, they
 | ||||
| // are always referenced by an [ObjClosure] which is the real first-class
 | ||||
| // representation of a function. This isn't strictly necessary if they function
 | ||||
| // has no upvalues, but lets the rest of the VM assume all called objects will
 | ||||
| // be closures.
 | ||||
| typedef struct | ||||
| { | ||||
|   Obj obj; | ||||
|    | ||||
|   ByteBuffer code; | ||||
|   ValueBuffer constants; | ||||
|    | ||||
|   // The module where this function was defined.
 | ||||
|   ObjModule* module; | ||||
| 
 | ||||
|   // The maximum number of stack slots this function may use.
 | ||||
|   int maxSlots; | ||||
|    | ||||
|   // The number of upvalues this function closes over.
 | ||||
|   int numUpvalues; | ||||
|    | ||||
|   // The number of parameters this function expects. Used to ensure that .call
 | ||||
|   // handles a mismatch between number of parameters and arguments. This will
 | ||||
|   // only be set for fns, and not ObjFns that represent methods or scripts.
 | ||||
|   int arity; | ||||
|   FnDebug* debug; | ||||
| } ObjFn; | ||||
| 
 | ||||
| // An instance of a first-class function and the environment it has closed over.
 | ||||
| // Unlike [ObjFn], this has captured the upvalues that the function accesses.
 | ||||
| typedef struct | ||||
| { | ||||
|   Obj obj; | ||||
| 
 | ||||
|   // The function that this closure is an instance of.
 | ||||
|   ObjFn* fn; | ||||
| 
 | ||||
|   // The upvalues this function has closed over.
 | ||||
|   ObjUpvalue* upvalues[FLEXIBLE_ARRAY]; | ||||
| } ObjClosure; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   // Pointer to the current (really next-to-be-executed) instruction in the
 | ||||
|   // function's bytecode.
 | ||||
|   uint8_t* ip; | ||||
|    | ||||
|   // The closure being executed.
 | ||||
|   ObjClosure* closure; | ||||
|    | ||||
|   // Pointer to the first stack slot used by this call frame. This will contain
 | ||||
|   // the receiver, followed by the function's parameters, then local variables
 | ||||
|   // and temporaries.
 | ||||
|   Value* stackStart; | ||||
| } CallFrame; | ||||
| 
 | ||||
| // Tracks how this fiber has been invoked, aside from the ways that can be
 | ||||
| // detected from the state of other fields in the fiber.
 | ||||
| typedef enum | ||||
| { | ||||
|   // The fiber is being run from another fiber using a call to `try()`.
 | ||||
|   FIBER_TRY, | ||||
|    | ||||
|   // The fiber was directly invoked by `runInterpreter()`. This means it's the
 | ||||
|   // initial fiber used by a call to `wrenCall()` or `wrenInterpret()`.
 | ||||
|   FIBER_ROOT, | ||||
|    | ||||
|   // The fiber is invoked some other way. If [caller] is `NULL` then the fiber
 | ||||
|   // was invoked using `call()`. If [numFrames] is zero, then the fiber has
 | ||||
|   // finished running and is done. If [numFrames] is one and that frame's `ip`
 | ||||
|   // points to the first byte of code, the fiber has not been started yet.
 | ||||
|   FIBER_OTHER, | ||||
| } FiberState; | ||||
| 
 | ||||
| typedef struct sObjFiber | ||||
| { | ||||
|   Obj obj; | ||||
|    | ||||
|   // The stack of value slots. This is used for holding local variables and
 | ||||
|   // temporaries while the fiber is executing. It heap-allocated and grown as
 | ||||
|   // needed.
 | ||||
|   Value* stack; | ||||
|    | ||||
|   // A pointer to one past the top-most value on the stack.
 | ||||
|   Value* stackTop; | ||||
|    | ||||
|   // The number of allocated slots in the stack array.
 | ||||
|   int stackCapacity; | ||||
|    | ||||
|   // The stack of call frames. This is a dynamic array that grows as needed but
 | ||||
|   // never shrinks.
 | ||||
|   CallFrame* frames; | ||||
|    | ||||
|   // The number of frames currently in use in [frames].
 | ||||
|   int numFrames; | ||||
|    | ||||
|   // The number of [frames] allocated.
 | ||||
|   int frameCapacity; | ||||
|    | ||||
|   // Pointer to the first node in the linked list of open upvalues that are
 | ||||
|   // pointing to values still on the stack. The head of the list will be the
 | ||||
|   // upvalue closest to the top of the stack, and then the list works downwards.
 | ||||
|   ObjUpvalue* openUpvalues; | ||||
|    | ||||
|   // The fiber that ran this one. If this fiber is yielded, control will resume
 | ||||
|   // to this one. May be `NULL`.
 | ||||
|   struct sObjFiber* caller; | ||||
|    | ||||
|   // If the fiber failed because of a runtime error, this will contain the
 | ||||
|   // error object. Otherwise, it will be null.
 | ||||
|   Value error; | ||||
|    | ||||
|   FiberState state; | ||||
| } ObjFiber; | ||||
| 
 | ||||
| typedef enum | ||||
| { | ||||
|   // A primitive method implemented in C in the VM. Unlike foreign methods,
 | ||||
|   // this can directly manipulate the fiber's stack.
 | ||||
|   METHOD_PRIMITIVE, | ||||
| 
 | ||||
|   // A externally-defined C method.
 | ||||
|   METHOD_FOREIGN, | ||||
| 
 | ||||
|   // A normal user-defined method.
 | ||||
|   METHOD_BLOCK, | ||||
|    | ||||
|   // No method for the given symbol.
 | ||||
|   METHOD_NONE | ||||
| } MethodType; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   MethodType type; | ||||
| 
 | ||||
|   // The method function itself. The [type] determines which field of the union
 | ||||
|   // is used.
 | ||||
|   union | ||||
|   { | ||||
|     Primitive primitive; | ||||
|     WrenForeignMethodFn foreign; | ||||
|     ObjClosure* closure; | ||||
|   } as; | ||||
| } Method; | ||||
| 
 | ||||
| DECLARE_BUFFER(Method, Method); | ||||
| 
 | ||||
| struct sObjClass | ||||
| { | ||||
|   Obj obj; | ||||
|   ObjClass* superclass; | ||||
| 
 | ||||
|   // The number of fields needed for an instance of this class, including all
 | ||||
|   // of its superclass fields.
 | ||||
|   int numFields; | ||||
| 
 | ||||
|   // The table of methods that are defined in or inherited by this class.
 | ||||
|   // Methods are called by symbol, and the symbol directly maps to an index in
 | ||||
|   // this table. This makes method calls fast at the expense of empty cells in
 | ||||
|   // the list for methods the class doesn't support.
 | ||||
|   //
 | ||||
|   // You can think of it as a hash table that never has collisions but has a
 | ||||
|   // really low load factor. Since methods are pretty small (just a type and a
 | ||||
|   // pointer), this should be a worthwhile trade-off.
 | ||||
|   MethodBuffer methods; | ||||
| 
 | ||||
|   // The name of the class.
 | ||||
|   ObjString* name; | ||||
| }; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   Obj obj; | ||||
|   uint8_t data[FLEXIBLE_ARRAY]; | ||||
| } ObjForeign; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   Obj obj; | ||||
|   Value fields[FLEXIBLE_ARRAY]; | ||||
| } ObjInstance; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   Obj obj; | ||||
| 
 | ||||
|   // The elements in the list.
 | ||||
|   ValueBuffer elements; | ||||
| } ObjList; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   // The entry's key, or UNDEFINED_VAL if the entry is not in use.
 | ||||
|   Value key; | ||||
| 
 | ||||
|   // The value associated with the key. If the key is UNDEFINED_VAL, this will
 | ||||
|   // be false to indicate an open available entry or true to indicate a
 | ||||
|   // tombstone -- an entry that was previously in use but was then deleted.
 | ||||
|   Value value; | ||||
| } MapEntry; | ||||
| 
 | ||||
| // A hash table mapping keys to values.
 | ||||
| //
 | ||||
| // We use something very simple: open addressing with linear probing. The hash
 | ||||
| // table is an array of entries. Each entry is a key-value pair. If the key is
 | ||||
| // the special UNDEFINED_VAL, it indicates no value is currently in that slot.
 | ||||
| // Otherwise, it's a valid key, and the value is the value associated with it.
 | ||||
| //
 | ||||
| // When entries are added, the array is dynamically scaled by GROW_FACTOR to
 | ||||
| // keep the number of filled slots under MAP_LOAD_PERCENT. Likewise, if the map
 | ||||
| // gets empty enough, it will be resized to a smaller array. When this happens,
 | ||||
| // all existing entries are rehashed and re-added to the new array.
 | ||||
| //
 | ||||
| // When an entry is removed, its slot is replaced with a "tombstone". This is an
 | ||||
| // entry whose key is UNDEFINED_VAL and whose value is TRUE_VAL. When probing
 | ||||
| // for a key, we will continue past tombstones, because the desired key may be
 | ||||
| // found after them if the key that was removed was part of a prior collision.
 | ||||
| // When the array gets resized, all tombstones are discarded.
 | ||||
| typedef struct | ||||
| { | ||||
|   Obj obj; | ||||
| 
 | ||||
|   // The number of entries allocated.
 | ||||
|   uint32_t capacity; | ||||
| 
 | ||||
|   // The number of entries in the map.
 | ||||
|   uint32_t count; | ||||
| 
 | ||||
|   // Pointer to a contiguous array of [capacity] entries.
 | ||||
|   MapEntry* entries; | ||||
| } ObjMap; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   Obj obj; | ||||
| 
 | ||||
|   // The beginning of the range.
 | ||||
|   double from; | ||||
| 
 | ||||
|   // The end of the range. May be greater or less than [from].
 | ||||
|   double to; | ||||
| 
 | ||||
|   // True if [to] is included in the range.
 | ||||
|   bool isInclusive; | ||||
| } ObjRange; | ||||
| 
 | ||||
| // An IEEE 754 double-precision float is a 64-bit value with bits laid out like:
 | ||||
| //
 | ||||
| // 1 Sign bit
 | ||||
| // | 11 Exponent bits
 | ||||
| // | |          52 Mantissa (i.e. fraction) bits
 | ||||
| // | |          |
 | ||||
| // S[Exponent-][Mantissa------------------------------------------]
 | ||||
| //
 | ||||
| // The details of how these are used to represent numbers aren't really
 | ||||
| // relevant here as long we don't interfere with them. The important bit is NaN.
 | ||||
| //
 | ||||
| // An IEEE double can represent a few magical values like NaN ("not a number"),
 | ||||
| // Infinity, and -Infinity. A NaN is any value where all exponent bits are set:
 | ||||
| //
 | ||||
| //  v--NaN bits
 | ||||
| // -11111111111----------------------------------------------------
 | ||||
| //
 | ||||
| // Here, "-" means "doesn't matter". Any bit sequence that matches the above is
 | ||||
| // a NaN. With all of those "-", it obvious there are a *lot* of different
 | ||||
| // bit patterns that all mean the same thing. NaN tagging takes advantage of
 | ||||
| // this. We'll use those available bit patterns to represent things other than
 | ||||
| // numbers without giving up any valid numeric values.
 | ||||
| //
 | ||||
| // NaN values come in two flavors: "signalling" and "quiet". The former are
 | ||||
| // intended to halt execution, while the latter just flow through arithmetic
 | ||||
| // operations silently. We want the latter. Quiet NaNs are indicated by setting
 | ||||
| // the highest mantissa bit:
 | ||||
| //
 | ||||
| //             v--Highest mantissa bit
 | ||||
| // -[NaN      ]1---------------------------------------------------
 | ||||
| //
 | ||||
| // If all of the NaN bits are set, it's not a number. Otherwise, it is.
 | ||||
| // That leaves all of the remaining bits as available for us to play with. We
 | ||||
| // stuff a few different kinds of things here: special singleton values like
 | ||||
| // "true", "false", and "null", and pointers to objects allocated on the heap.
 | ||||
| // We'll use the sign bit to distinguish singleton values from pointers. If
 | ||||
| // it's set, it's a pointer.
 | ||||
| //
 | ||||
| // v--Pointer or singleton?
 | ||||
| // S[NaN      ]1---------------------------------------------------
 | ||||
| //
 | ||||
| // For singleton values, we just enumerate the different values. We'll use the
 | ||||
| // low bits of the mantissa for that, and only need a few:
 | ||||
| //
 | ||||
| //                                                 3 Type bits--v
 | ||||
| // 0[NaN      ]1------------------------------------------------[T]
 | ||||
| //
 | ||||
| // For pointers, we are left with 51 bits of mantissa to store an address.
 | ||||
| // That's more than enough room for a 32-bit address. Even 64-bit machines
 | ||||
| // only actually use 48 bits for addresses, so we've got plenty. We just stuff
 | ||||
| // the address right into the mantissa.
 | ||||
| //
 | ||||
| // Ta-da, double precision numbers, pointers, and a bunch of singleton values,
 | ||||
| // all stuffed into a single 64-bit sequence. Even better, we don't have to
 | ||||
| // do any masking or work to extract number values: they are unmodified. This
 | ||||
| // means math on numbers is fast.
 | ||||
| #if WREN_NAN_TAGGING | ||||
| 
 | ||||
| // A mask that selects the sign bit.
 | ||||
| #define SIGN_BIT ((uint64_t)1 << 63) | ||||
| 
 | ||||
| // The bits that must be set to indicate a quiet NaN.
 | ||||
| #define QNAN ((uint64_t)0x7ffc000000000000) | ||||
| 
 | ||||
| // If the NaN bits are set, it's not a number.
 | ||||
| #define IS_NUM(value) (((value) & QNAN) != QNAN) | ||||
| 
 | ||||
| // An object pointer is a NaN with a set sign bit.
 | ||||
| #define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT)) | ||||
| 
 | ||||
| #define IS_FALSE(value)     ((value) == FALSE_VAL) | ||||
| #define IS_NULL(value)      ((value) == NULL_VAL) | ||||
| #define IS_UNDEFINED(value) ((value) == UNDEFINED_VAL) | ||||
| 
 | ||||
| // Masks out the tag bits used to identify the singleton value.
 | ||||
| #define MASK_TAG (7) | ||||
| 
 | ||||
| // Tag values for the different singleton values.
 | ||||
| #define TAG_NAN       (0) | ||||
| #define TAG_NULL      (1) | ||||
| #define TAG_FALSE     (2) | ||||
| #define TAG_TRUE      (3) | ||||
| #define TAG_UNDEFINED (4) | ||||
| #define TAG_UNUSED2   (5) | ||||
| #define TAG_UNUSED3   (6) | ||||
| #define TAG_UNUSED4   (7) | ||||
| 
 | ||||
| // Value -> 0 or 1.
 | ||||
| #define AS_BOOL(value) ((value) == TRUE_VAL) | ||||
| 
 | ||||
| // Value -> Obj*.
 | ||||
| #define AS_OBJ(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN))) | ||||
| 
 | ||||
| // Singleton values.
 | ||||
| #define NULL_VAL      ((Value)(uint64_t)(QNAN | TAG_NULL)) | ||||
| #define FALSE_VAL     ((Value)(uint64_t)(QNAN | TAG_FALSE)) | ||||
| #define TRUE_VAL      ((Value)(uint64_t)(QNAN | TAG_TRUE)) | ||||
| #define UNDEFINED_VAL ((Value)(uint64_t)(QNAN | TAG_UNDEFINED)) | ||||
| 
 | ||||
| // Gets the singleton type tag for a Value (which must be a singleton).
 | ||||
| #define GET_TAG(value) ((int)((value) & MASK_TAG)) | ||||
| 
 | ||||
| #else | ||||
| 
 | ||||
| // Value -> 0 or 1.
 | ||||
| #define AS_BOOL(value) ((value).type == VAL_TRUE) | ||||
| 
 | ||||
| // Value -> Obj*.
 | ||||
| #define AS_OBJ(v) ((v).as.obj) | ||||
| 
 | ||||
| // Determines if [value] is a garbage-collected object or not.
 | ||||
| #define IS_OBJ(value) ((value).type == VAL_OBJ) | ||||
| 
 | ||||
| #define IS_FALSE(value)     ((value).type == VAL_FALSE) | ||||
| #define IS_NULL(value)      ((value).type == VAL_NULL) | ||||
| #define IS_NUM(value)       ((value).type == VAL_NUM) | ||||
| #define IS_UNDEFINED(value) ((value).type == VAL_UNDEFINED) | ||||
| 
 | ||||
| // Singleton values.
 | ||||
| #define FALSE_VAL     ((Value){ VAL_FALSE, { 0 } }) | ||||
| #define NULL_VAL      ((Value){ VAL_NULL, { 0 } }) | ||||
| #define TRUE_VAL      ((Value){ VAL_TRUE, { 0 } }) | ||||
| #define UNDEFINED_VAL ((Value){ VAL_UNDEFINED, { 0 } }) | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| // A union to let us reinterpret a double as raw bits and back.
 | ||||
| typedef union | ||||
| { | ||||
|   uint64_t bits64; | ||||
|   uint32_t bits32[2]; | ||||
|   double num; | ||||
| } DoubleBits; | ||||
| 
 | ||||
| // Creates a new "raw" class. It has no metaclass or superclass whatsoever.
 | ||||
| // This is only used for bootstrapping the initial Object and Class classes,
 | ||||
| // which are a little special.
 | ||||
| ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name); | ||||
| 
 | ||||
| // Makes [superclass] the superclass of [subclass], and causes subclass to
 | ||||
| // inherit its methods. This should be called before any methods are defined
 | ||||
| // on subclass.
 | ||||
| void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass); | ||||
| 
 | ||||
| // Creates a new class object as well as its associated metaclass.
 | ||||
| ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, | ||||
|                        ObjString* name); | ||||
| 
 | ||||
| void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method); | ||||
| 
 | ||||
| // Creates a new closure object that invokes [fn]. Allocates room for its
 | ||||
| // upvalues, but assumes outside code will populate it.
 | ||||
| ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn); | ||||
| 
 | ||||
| // Creates a new fiber object that will invoke [closure].
 | ||||
| ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure); | ||||
| 
 | ||||
| // Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at
 | ||||
| // [stackStart].
 | ||||
| static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber, | ||||
|                                        ObjClosure* closure, Value* stackStart) | ||||
| { | ||||
|   // The caller should have ensured we already have enough capacity.
 | ||||
|   ASSERT(fiber->frameCapacity > fiber->numFrames, "No memory for call frame."); | ||||
|    | ||||
|   CallFrame* frame = &fiber->frames[fiber->numFrames++]; | ||||
|   frame->stackStart = stackStart; | ||||
|   frame->closure = closure; | ||||
|   frame->ip = closure->fn->code.data; | ||||
| } | ||||
| 
 | ||||
| // Ensures [fiber]'s stack has at least [needed] slots.
 | ||||
| void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed); | ||||
| 
 | ||||
| static inline bool wrenHasError(const ObjFiber* fiber) | ||||
| { | ||||
|   return !IS_NULL(fiber->error); | ||||
| } | ||||
| 
 | ||||
| ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size); | ||||
| 
 | ||||
| // Creates a new empty function. Before being used, it must have code,
 | ||||
| // constants, etc. added to it.
 | ||||
| ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots); | ||||
| 
 | ||||
| void wrenFunctionBindName(WrenVM* vm, ObjFn* fn, const char* name, int length); | ||||
| 
 | ||||
| // Creates a new instance of the given [classObj].
 | ||||
| Value wrenNewInstance(WrenVM* vm, ObjClass* classObj); | ||||
| 
 | ||||
| // Creates a new list with [numElements] elements (which are left
 | ||||
| // uninitialized.)
 | ||||
| ObjList* wrenNewList(WrenVM* vm, uint32_t numElements); | ||||
| 
 | ||||
| // Inserts [value] in [list] at [index], shifting down the other elements.
 | ||||
| void wrenListInsert(WrenVM* vm, ObjList* list, Value value, uint32_t index); | ||||
| 
 | ||||
| // Removes and returns the item at [index] from [list].
 | ||||
| Value wrenListRemoveAt(WrenVM* vm, ObjList* list, uint32_t index); | ||||
| 
 | ||||
| // Creates a new empty map.
 | ||||
| ObjMap* wrenNewMap(WrenVM* vm); | ||||
| 
 | ||||
| // Looks up [key] in [map]. If found, returns the value. Otherwise, returns
 | ||||
| // `UNDEFINED_VAL`.
 | ||||
| Value wrenMapGet(ObjMap* map, Value key); | ||||
| 
 | ||||
| // Associates [key] with [value] in [map].
 | ||||
| void wrenMapSet(WrenVM* vm, ObjMap* map, Value key, Value value); | ||||
| 
 | ||||
| void wrenMapClear(WrenVM* vm, ObjMap* map); | ||||
| 
 | ||||
| // Removes [key] from [map], if present. Returns the value for the key if found
 | ||||
| // or `NULL_VAL` otherwise.
 | ||||
| Value wrenMapRemoveKey(WrenVM* vm, ObjMap* map, Value key); | ||||
| 
 | ||||
| // Creates a new module.
 | ||||
| ObjModule* wrenNewModule(WrenVM* vm, ObjString* name); | ||||
| 
 | ||||
| // Creates a new range from [from] to [to].
 | ||||
| Value wrenNewRange(WrenVM* vm, double from, double to, bool isInclusive); | ||||
| 
 | ||||
| // Creates a new string object and copies [text] into it.
 | ||||
| //
 | ||||
| // [text] must be non-NULL.
 | ||||
| Value wrenNewString(WrenVM* vm, const char* text); | ||||
| 
 | ||||
| // Creates a new string object of [length] and copies [text] into it.
 | ||||
| //
 | ||||
| // [text] may be NULL if [length] is zero.
 | ||||
| Value wrenNewStringLength(WrenVM* vm, const char* text, size_t length); | ||||
| 
 | ||||
| // Creates a new string object by taking a range of characters from [source].
 | ||||
| // The range starts at [start], contains [count] bytes, and increments by
 | ||||
| // [step].
 | ||||
| Value wrenNewStringFromRange(WrenVM* vm, ObjString* source, int start, | ||||
|                              uint32_t count, int step); | ||||
| 
 | ||||
| // Produces a string representation of [value].
 | ||||
| Value wrenNumToString(WrenVM* vm, double value); | ||||
| 
 | ||||
| // Creates a new formatted string from [format] and any additional arguments
 | ||||
| // used in the format string.
 | ||||
| //
 | ||||
| // This is a very restricted flavor of formatting, intended only for internal
 | ||||
| // use by the VM. Two formatting characters are supported, each of which reads
 | ||||
| // the next argument as a certain type:
 | ||||
| //
 | ||||
| // $ - A C string.
 | ||||
| // @ - A Wren string object.
 | ||||
| Value wrenStringFormat(WrenVM* vm, const char* format, ...); | ||||
| 
 | ||||
| // Creates a new string containing the UTF-8 encoding of [value].
 | ||||
| Value wrenStringFromCodePoint(WrenVM* vm, int value); | ||||
| 
 | ||||
| // Creates a new string from the integer representation of a byte
 | ||||
| Value wrenStringFromByte(WrenVM* vm, uint8_t value); | ||||
| 
 | ||||
| // Creates a new string containing the code point in [string] starting at byte
 | ||||
| // [index]. If [index] points into the middle of a UTF-8 sequence, returns an
 | ||||
| // empty string.
 | ||||
| Value wrenStringCodePointAt(WrenVM* vm, ObjString* string, uint32_t index); | ||||
| 
 | ||||
| // Search for the first occurence of [needle] within [haystack] and returns its
 | ||||
| // zero-based offset. Returns `UINT32_MAX` if [haystack] does not contain
 | ||||
| // [needle].
 | ||||
| uint32_t wrenStringFind(ObjString* haystack, ObjString* needle, | ||||
|                         uint32_t startIndex); | ||||
| 
 | ||||
| // Returns true if [a] and [b] represent the same string.
 | ||||
| static inline bool wrenStringEqualsCString(const ObjString* a, | ||||
|                                            const char* b, size_t length) | ||||
| { | ||||
|   return a->length == length && memcmp(a->value, b, length) == 0; | ||||
| } | ||||
| 
 | ||||
| // Creates a new open upvalue pointing to [value] on the stack.
 | ||||
| ObjUpvalue* wrenNewUpvalue(WrenVM* vm, Value* value); | ||||
| 
 | ||||
| // Mark [obj] as reachable and still in use. This should only be called
 | ||||
| // during the sweep phase of a garbage collection.
 | ||||
| void wrenGrayObj(WrenVM* vm, Obj* obj); | ||||
| 
 | ||||
| // Mark [value] as reachable and still in use. This should only be called
 | ||||
| // during the sweep phase of a garbage collection.
 | ||||
| void wrenGrayValue(WrenVM* vm, Value value); | ||||
| 
 | ||||
| // Mark the values in [buffer] as reachable and still in use. This should only
 | ||||
| // be called during the sweep phase of a garbage collection.
 | ||||
| void wrenGrayBuffer(WrenVM* vm, ValueBuffer* buffer); | ||||
| 
 | ||||
| // Processes every object in the gray stack until all reachable objects have
 | ||||
| // been marked. After that, all objects are either white (freeable) or black
 | ||||
| // (in use and fully traversed).
 | ||||
| void wrenBlackenObjects(WrenVM* vm); | ||||
| 
 | ||||
| // Releases all memory owned by [obj], including [obj] itself.
 | ||||
| void wrenFreeObj(WrenVM* vm, Obj* obj); | ||||
| 
 | ||||
| // Returns the class of [value].
 | ||||
| //
 | ||||
| // Unlike wrenGetClassInline in wren_vm.h, this is not inlined. Inlining helps
 | ||||
| // performance (significantly) in some cases, but degrades it in others. The
 | ||||
| // ones used by the implementation were chosen to give the best results in the
 | ||||
| // benchmarks.
 | ||||
| ObjClass* wrenGetClass(WrenVM* vm, Value value); | ||||
| 
 | ||||
| // Returns true if [a] and [b] are strictly the same value. This is identity
 | ||||
| // for object values, and value equality for unboxed values.
 | ||||
| static inline bool wrenValuesSame(Value a, Value b) | ||||
| { | ||||
| #if WREN_NAN_TAGGING | ||||
|   // Value types have unique bit representations and we compare object types
 | ||||
|   // by identity (i.e. pointer), so all we need to do is compare the bits.
 | ||||
|   return a == b; | ||||
| #else | ||||
|   if (a.type != b.type) return false; | ||||
|   if (a.type == VAL_NUM) return a.as.num == b.as.num; | ||||
|   return a.as.obj == b.as.obj; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| // Returns true if [a] and [b] are equivalent. Immutable values (null, bools,
 | ||||
| // numbers, ranges, and strings) are equal if they have the same data. All
 | ||||
| // other values are equal if they are identical objects.
 | ||||
| bool wrenValuesEqual(Value a, Value b); | ||||
| 
 | ||||
| // Returns true if [value] is a bool. Do not call this directly, instead use
 | ||||
| // [IS_BOOL].
 | ||||
| static inline bool wrenIsBool(Value value) | ||||
| { | ||||
| #if WREN_NAN_TAGGING | ||||
|   return value == TRUE_VAL || value == FALSE_VAL; | ||||
| #else | ||||
|   return value.type == VAL_FALSE || value.type == VAL_TRUE; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| // Returns true if [value] is an object of type [type]. Do not call this
 | ||||
| // directly, instead use the [IS___] macro for the type in question.
 | ||||
| static inline bool wrenIsObjType(Value value, ObjType type) | ||||
| { | ||||
|   return IS_OBJ(value) && AS_OBJ(value)->type == type; | ||||
| } | ||||
| 
 | ||||
| // Converts the raw object pointer [obj] to a [Value].
 | ||||
| static inline Value wrenObjectToValue(Obj* obj) | ||||
| { | ||||
| #if WREN_NAN_TAGGING | ||||
|   // The triple casting is necessary here to satisfy some compilers:
 | ||||
|   // 1. (uintptr_t) Convert the pointer to a number of the right size.
 | ||||
|   // 2. (uint64_t)  Pad it up to 64 bits in 32-bit builds.
 | ||||
|   // 3. Or in the bits to make a tagged Nan.
 | ||||
|   // 4. Cast to a typedef'd value.
 | ||||
|   return (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj)); | ||||
| #else | ||||
|   Value value; | ||||
|   value.type = VAL_OBJ; | ||||
|   value.as.obj = obj; | ||||
|   return value; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| // Interprets [value] as a [double].
 | ||||
| static inline double wrenValueToNum(Value value) | ||||
| { | ||||
| #if WREN_NAN_TAGGING | ||||
|   DoubleBits data; | ||||
|   data.bits64 = value; | ||||
|   return data.num; | ||||
| #else | ||||
|   return value.as.num; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| // Converts [num] to a [Value].
 | ||||
| static inline Value wrenNumToValue(double num) | ||||
| { | ||||
| #if WREN_NAN_TAGGING | ||||
|   DoubleBits data; | ||||
|   data.num = num; | ||||
|   return data.bits64; | ||||
| #else | ||||
|   Value value; | ||||
|   value.type = VAL_NUM; | ||||
|   value.as.num = num; | ||||
|   return value; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										1778
									
								
								src/logic/wren/vm/wren_vm.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1778
									
								
								src/logic/wren/vm/wren_vm.c
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										236
									
								
								src/logic/wren/vm/wren_vm.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								src/logic/wren/vm/wren_vm.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,236 @@ | |||
| #ifndef wren_vm_h | ||||
| #define wren_vm_h | ||||
| 
 | ||||
| #include "wren_common.h" | ||||
| #include "wren_compiler.h" | ||||
| #include "wren_value.h" | ||||
| #include "wren_utils.h" | ||||
| 
 | ||||
| // The maximum number of temporary objects that can be made visible to the GC
 | ||||
| // at one time.
 | ||||
| #define WREN_MAX_TEMP_ROOTS 5 | ||||
| 
 | ||||
| typedef enum | ||||
| { | ||||
|   #define OPCODE(name, _) CODE_##name, | ||||
|   #include "wren_opcodes.h" | ||||
|   #undef OPCODE | ||||
| } Code; | ||||
| 
 | ||||
| // A handle to a value, basically just a linked list of extra GC roots.
 | ||||
| //
 | ||||
| // Note that even non-heap-allocated values can be stored here.
 | ||||
| struct WrenHandle | ||||
| { | ||||
|   Value value; | ||||
| 
 | ||||
|   WrenHandle* prev; | ||||
|   WrenHandle* next; | ||||
| }; | ||||
| 
 | ||||
| struct WrenVM | ||||
| { | ||||
|   ObjClass* boolClass; | ||||
|   ObjClass* classClass; | ||||
|   ObjClass* fiberClass; | ||||
|   ObjClass* fnClass; | ||||
|   ObjClass* listClass; | ||||
|   ObjClass* mapClass; | ||||
|   ObjClass* nullClass; | ||||
|   ObjClass* numClass; | ||||
|   ObjClass* objectClass; | ||||
|   ObjClass* rangeClass; | ||||
|   ObjClass* stringClass; | ||||
| 
 | ||||
|   // The fiber that is currently running.
 | ||||
|   ObjFiber* fiber; | ||||
| 
 | ||||
|   // The loaded modules. Each key is an ObjString (except for the main module,
 | ||||
|   // whose key is null) for the module's name and the value is the ObjModule
 | ||||
|   // for the module.
 | ||||
|   ObjMap* modules; | ||||
|    | ||||
|   // The most recently imported module. More specifically, the module whose
 | ||||
|   // code has most recently finished executing.
 | ||||
|   //
 | ||||
|   // Not treated like a GC root since the module is already in [modules].
 | ||||
|   ObjModule* lastModule; | ||||
| 
 | ||||
|   // Memory management data:
 | ||||
| 
 | ||||
|   // The number of bytes that are known to be currently allocated. Includes all
 | ||||
|   // memory that was proven live after the last GC, as well as any new bytes
 | ||||
|   // that were allocated since then. Does *not* include bytes for objects that
 | ||||
|   // were freed since the last GC.
 | ||||
|   size_t bytesAllocated; | ||||
| 
 | ||||
|   // The number of total allocated bytes that will trigger the next GC.
 | ||||
|   size_t nextGC; | ||||
| 
 | ||||
|   // The first object in the linked list of all currently allocated objects.
 | ||||
|   Obj* first; | ||||
| 
 | ||||
|   // The "gray" set for the garbage collector. This is the stack of unprocessed
 | ||||
|   // objects while a garbage collection pass is in process.
 | ||||
|   Obj** gray; | ||||
|   int grayCount; | ||||
|   int grayCapacity; | ||||
| 
 | ||||
|   // The list of temporary roots. This is for temporary or new objects that are
 | ||||
|   // not otherwise reachable but should not be collected.
 | ||||
|   //
 | ||||
|   // They are organized as a stack of pointers stored in this array. This
 | ||||
|   // implies that temporary roots need to have stack semantics: only the most
 | ||||
|   // recently pushed object can be released.
 | ||||
|   Obj* tempRoots[WREN_MAX_TEMP_ROOTS]; | ||||
| 
 | ||||
|   int numTempRoots; | ||||
|    | ||||
|   // Pointer to the first node in the linked list of active handles or NULL if
 | ||||
|   // there are none.
 | ||||
|   WrenHandle* handles; | ||||
|    | ||||
|   // Pointer to the bottom of the range of stack slots available for use from
 | ||||
|   // the C API. During a foreign method, this will be in the stack of the fiber
 | ||||
|   // that is executing a method.
 | ||||
|   //
 | ||||
|   // If not in a foreign method, this is initially NULL. If the user requests
 | ||||
|   // slots by calling wrenEnsureSlots(), a stack is created and this is
 | ||||
|   // initialized.
 | ||||
|   Value* apiStack; | ||||
| 
 | ||||
|   WrenConfiguration config; | ||||
|    | ||||
|   // Compiler and debugger data:
 | ||||
| 
 | ||||
|   // The compiler that is currently compiling code. This is used so that heap
 | ||||
|   // allocated objects used by the compiler can be found if a GC is kicked off
 | ||||
|   // in the middle of a compile.
 | ||||
|   Compiler* compiler; | ||||
| 
 | ||||
|   // There is a single global symbol table for all method names on all classes.
 | ||||
|   // Method calls are dispatched directly by index in this table.
 | ||||
|   SymbolTable methodNames; | ||||
| }; | ||||
| 
 | ||||
| // A generic allocation function that handles all explicit memory management.
 | ||||
| // It's used like so:
 | ||||
| //
 | ||||
| // - To allocate new memory, [memory] is NULL and [oldSize] is zero. It should
 | ||||
| //   return the allocated memory or NULL on failure.
 | ||||
| //
 | ||||
| // - To attempt to grow an existing allocation, [memory] is the memory,
 | ||||
| //   [oldSize] is its previous size, and [newSize] is the desired size.
 | ||||
| //   It should return [memory] if it was able to grow it in place, or a new
 | ||||
| //   pointer if it had to move it.
 | ||||
| //
 | ||||
| // - To shrink memory, [memory], [oldSize], and [newSize] are the same as above
 | ||||
| //   but it will always return [memory].
 | ||||
| //
 | ||||
| // - To free memory, [memory] will be the memory to free and [newSize] and
 | ||||
| //   [oldSize] will be zero. It should return NULL.
 | ||||
| void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize); | ||||
| 
 | ||||
| // Invoke the finalizer for the foreign object referenced by [foreign].
 | ||||
| void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign); | ||||
| 
 | ||||
| // Creates a new [WrenHandle] for [value].
 | ||||
| WrenHandle* wrenMakeHandle(WrenVM* vm, Value value); | ||||
| 
 | ||||
| // Compile [source] in the context of [module] and wrap in a fiber that can
 | ||||
| // execute it.
 | ||||
| //
 | ||||
| // Returns NULL if a compile error occurred.
 | ||||
| ObjClosure* wrenCompileSource(WrenVM* vm, const char* module, | ||||
|                               const char* source, bool isExpression, | ||||
|                               bool printErrors); | ||||
| 
 | ||||
| // Looks up a variable from a previously-loaded module.
 | ||||
| //
 | ||||
| // Aborts the current fiber if the module or variable could not be found.
 | ||||
| Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName); | ||||
| 
 | ||||
| // Returns the value of the module-level variable named [name] in the main
 | ||||
| // module.
 | ||||
| Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name); | ||||
| 
 | ||||
| // Adds a new implicitly declared top-level variable named [name] to [module]
 | ||||
| // based on a use site occurring on [line].
 | ||||
| //
 | ||||
| // Does not check to see if a variable with that name is already declared or
 | ||||
| // defined. Returns the symbol for the new variable or -2 if there are too many
 | ||||
| // variables defined.
 | ||||
| int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name, | ||||
|                         size_t length, int line); | ||||
| 
 | ||||
| // Adds a new top-level variable named [name] to [module].
 | ||||
| //
 | ||||
| // Returns the symbol for the new variable, -1 if a variable with the given name
 | ||||
| // is already defined, or -2 if there are too many variables defined.
 | ||||
| int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name, | ||||
|                        size_t length, Value value); | ||||
| 
 | ||||
| // Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs]
 | ||||
| // arguments (including the receiver) to be on the top of the stack already.
 | ||||
| static inline void wrenCallFunction(WrenVM* vm, ObjFiber* fiber, | ||||
|                                     ObjClosure* closure, int numArgs) | ||||
| { | ||||
|   // Grow the call frame array if needed.
 | ||||
|   if (fiber->numFrames + 1 > fiber->frameCapacity) | ||||
|   { | ||||
|     int max = fiber->frameCapacity * 2; | ||||
|     fiber->frames = (CallFrame*)wrenReallocate(vm, fiber->frames, | ||||
|         sizeof(CallFrame) * fiber->frameCapacity, sizeof(CallFrame) * max); | ||||
|     fiber->frameCapacity = max; | ||||
|   } | ||||
|    | ||||
|   // Grow the stack if needed.
 | ||||
|   int stackSize = (int)(fiber->stackTop - fiber->stack); | ||||
|   int needed = stackSize + closure->fn->maxSlots; | ||||
|   wrenEnsureStack(vm, fiber, needed); | ||||
|    | ||||
|   wrenAppendCallFrame(vm, fiber, closure, fiber->stackTop - numArgs); | ||||
| } | ||||
| 
 | ||||
| // Marks [obj] as a GC root so that it doesn't get collected.
 | ||||
| void wrenPushRoot(WrenVM* vm, Obj* obj); | ||||
| 
 | ||||
| // Removes the most recently pushed temporary root.
 | ||||
| void wrenPopRoot(WrenVM* vm); | ||||
| 
 | ||||
| // Returns the class of [value].
 | ||||
| //
 | ||||
| // Defined here instead of in wren_value.h because it's critical that this be
 | ||||
| // inlined. That means it must be defined in the header, but the wren_value.h
 | ||||
| // header doesn't have a full definitely of WrenVM yet.
 | ||||
| static inline ObjClass* wrenGetClassInline(WrenVM* vm, Value value) | ||||
| { | ||||
|   if (IS_NUM(value)) return vm->numClass; | ||||
|   if (IS_OBJ(value)) return AS_OBJ(value)->classObj; | ||||
| 
 | ||||
| #if WREN_NAN_TAGGING | ||||
|   switch (GET_TAG(value)) | ||||
|   { | ||||
|     case TAG_FALSE:     return vm->boolClass; break; | ||||
|     case TAG_NAN:       return vm->numClass; break; | ||||
|     case TAG_NULL:      return vm->nullClass; break; | ||||
|     case TAG_TRUE:      return vm->boolClass; break; | ||||
|     case TAG_UNDEFINED: UNREACHABLE(); | ||||
|   } | ||||
| #else | ||||
|   switch (value.type) | ||||
|   { | ||||
|     case VAL_FALSE:     return vm->boolClass; | ||||
|     case VAL_NULL:      return vm->nullClass; | ||||
|     case VAL_NUM:       return vm->numClass; | ||||
|     case VAL_TRUE:      return vm->boolClass; | ||||
|     case VAL_OBJ:       return AS_OBJ(value)->classObj; | ||||
|     case VAL_UNDEFINED: UNREACHABLE(); | ||||
|   } | ||||
| #endif | ||||
| 
 | ||||
|   UNREACHABLE(); | ||||
|   return NULL; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										11
									
								
								src/main.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/main.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| #include "engine/engine.hpp" | ||||
| #include "logic/logic.hpp" | ||||
| 
 | ||||
| int main() { | ||||
|     Engine e; | ||||
|     Logic l(&e); | ||||
| 
 | ||||
|     l.interpret(); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue