1 /** 2 This module implements the Interpreter class, the main class used by host applications to run scripts 3 4 ──────────────────────────────────────────────────────────────────────────────── 5 6 Copyright (C) 2021 pillager86.rf.gd 7 8 This program is free software: you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation, either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT ANY 14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 15 PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License along with 18 this program. If not, see <https://www.gnu.org/licenses/>. 19 */ 20 module mildew.interpreter; 21 22 import std.typecons; 23 24 import mildew.compiler; 25 import mildew.environment; 26 import mildew.exceptions: ScriptRuntimeException; 27 import mildew.lexer: Token, Lexer; 28 import mildew.nodes; 29 import mildew.parser; 30 import mildew.types; 31 import mildew.vm; 32 33 /** 34 * This is the main interface for the host application to interact with scripts. 35 */ 36 class Interpreter 37 { 38 public: 39 40 /** 41 * Constructs a new Interpreter with a global environment. Note that all calls to evaluate 42 * run in a new environment below the global environment. This allows keywords such as let and const 43 * to not pollute the global namespace. However, scripts can use var to declare variables that 44 * are global. 45 * Params: 46 * printVMDebugInfo = This option, if true, indicates to print very verbose data while executing bytecode. 47 */ 48 this(bool printVMDebugInfo = false) 49 { 50 _globalEnvironment = new Environment(this); 51 _compiler = new Compiler(); 52 _printVMDebugInfo = printVMDebugInfo; 53 _vm = new VirtualMachine(_globalEnvironment); 54 } 55 56 /** 57 * Initializes the Mildew standard library, such as Object, Math, and console namespaces. This 58 * is optional and is not called by the constructor. For a script to use these methods such as 59 * console.log this must be called first. It is also possible to only call specific 60 * initialize*Library functions and/or force set globals from them to UNDEFINED. 61 */ 62 void initializeStdlib() 63 { 64 import mildew.types.bindings: initializeTypesLibrary; 65 import mildew.stdlib.global: initializeGlobalLibrary; 66 import mildew.stdlib.console: initializeConsoleLibrary; 67 import mildew.stdlib.date: initializeDateLibrary; 68 import mildew.stdlib.generator: initializeGeneratorLibrary; 69 import mildew.stdlib.map: initializeMapLibrary; 70 import mildew.stdlib.math: initializeMathLibrary; 71 import mildew.stdlib.regexp: initializeRegExpLibrary; 72 import mildew.stdlib.system: initializeSystemLib; 73 initializeTypesLibrary(this); 74 initializeGlobalLibrary(this); 75 initializeConsoleLibrary(this); 76 initializeDateLibrary(this); 77 initializeGeneratorLibrary(this); 78 initializeMapLibrary(this); 79 initializeMathLibrary(this); 80 initializeRegExpLibrary(this); 81 initializeSystemLib(this); 82 } 83 84 /** 85 * This is the main entry point for evaluating a script program. If the useVM option was set in the 86 * constructor, bytecode compilation and execution will be used, otherwise tree walking. 87 * Params: 88 * code = This is the source code of a script to be executed. 89 * printDisasm = If VM mode is set, print the disassembly of bytecode before running if true. 90 * fromScript = This parameter is reserved for internal use and should be left as false 91 * Returns: 92 * If the script has a return statement with an expression, this value will be the result of that expression 93 * otherwise it will be ScriptAny.UNDEFINED 94 */ 95 ScriptAny evaluate(in string code, bool printDisasm=false, bool fromScript=false) 96 { 97 // TODO: evaluate should run all compiled chunks as a function call with module and exports 98 // parameters. 99 auto chunk = _compiler.compile(code); 100 if(printDisasm) 101 _vm.printChunk(chunk, true); 102 103 if(fromScript) 104 { 105 auto func = new ScriptFunction("chunk", [], chunk.bytecode, false, false, chunk.constTable); 106 func = func.copyCompiled(_globalEnvironment); 107 return _vm.runFunction(func, ScriptAny.UNDEFINED, []); 108 } 109 else 110 { 111 return _vm.run(chunk, _printVMDebugInfo); 112 } 113 } 114 115 /** 116 * Evaluates a file that can be either binary bytecode or textual source code. 117 * Params: 118 * pathName = the location of the code file in the file system. 119 * printDisasm = Whether or not bytecode disassembly should be printed before running 120 * fromScript = This should be left to false and will be used internally 121 * Returns: 122 * The result of evaluating the file, undefined if no return statement. 123 */ 124 ScriptAny evaluateFile(in string pathName, bool printDisasm=false, bool fromScript=false) 125 { 126 // TODO if fromScript is true, module and exports parameter that can be checked 127 // and made the return value 128 import std.stdio: File, writefln; 129 import mildew.util.encode: decode; 130 131 File inputFile = File(pathName, "rb"); 132 auto raw = new ubyte[inputFile.size]; 133 raw = inputFile.rawRead(raw); 134 if(raw.length > 0 && raw[0] == 0x01) 135 { 136 auto chunk = Chunk.deserialize(raw); 137 if(printDisasm) 138 _vm.printChunk(chunk); 139 if(fromScript) 140 { 141 auto func = new ScriptFunction(pathName, [], chunk.bytecode, false, false, chunk.constTable); 142 func = func.copyCompiled(_globalEnvironment); 143 return _vm.runFunction(func, ScriptAny.UNDEFINED, []); 144 } 145 else 146 { 147 return _vm.run(chunk, _printVMDebugInfo); 148 } 149 } 150 else 151 { 152 auto source = cast(string)raw; 153 return evaluate(source, printDisasm, fromScript); 154 } 155 } 156 157 // TODO: Create an evaluate function with default exception handling with file name info 158 159 /** 160 * Sets a global variable or constant without checking whether or not the variable or const was already 161 * declared. This is used by host applications to define custom functions or objects. 162 * Params: 163 * name = The name of the global variable. 164 * value = The value the variable should be set to. 165 * isConst = Whether or not the script can overwrite the global. 166 */ 167 void forceSetGlobal(T)(in string name, T value, bool isConst=false) 168 { 169 _globalEnvironment.forceSetVarOrConst(name, ScriptAny(value), isConst); 170 } 171 172 /** 173 * Unsets a variable or constant in the global environment. Used by host applications to remove 174 * items that were loaded by the standard library load functions. Specific functions of 175 * script classes can be removed by modifying the "prototype" field of their constructor. 176 * Params: 177 * name = The name of the global variable to unset 178 */ 179 void forceUnsetGlobal(in string name) 180 { 181 _globalEnvironment.forceRemoveVarOrConst(name); 182 } 183 184 /** 185 * Run the VM queued fibers. This API is still being worked on. 186 */ 187 void runVMFibers() 188 { 189 _vm.runQueue(); 190 } 191 192 /// whether or not debug info should be printed between each VM instruction 193 bool printVMDebugInfo() const 194 { 195 return _printVMDebugInfo; 196 } 197 198 /// Virtual machine property should never at any point be null 199 VirtualMachine vm() { return _vm; } 200 201 private: 202 203 // TODO a registry to keep track of loaded/run files. 204 205 Compiler _compiler; 206 bool _printVMDebugInfo; 207 VirtualMachine _vm; 208 Environment _globalEnvironment; 209 } 210