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