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      *  printDisasm = If this is set to true, bytecode disassembly of each program will be printed before execution.
47      *  printSteps = If this is set to true, detailed step by step execution of bytecode in the VM will be printed.
48      */
49     this(bool printDisasm = false, bool printSteps = false)
50     {
51         _globalEnvironment = new Environment(this);
52         _compiler = new Compiler();
53         _vm = new VirtualMachine(_globalEnvironment, printDisasm, printSteps);
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.buffers: initializeBuffersLibrary;
67         import mildew.stdlib.console: initializeConsoleLibrary;
68         import mildew.stdlib.date: initializeDateLibrary;
69         import mildew.stdlib.generator: initializeGeneratorLibrary;
70         import mildew.stdlib.json: initializeJSONLibrary;
71         import mildew.stdlib.map: initializeMapLibrary;
72         import mildew.stdlib.math: initializeMathLibrary;
73         import mildew.stdlib.regexp: initializeRegExpLibrary;
74         import mildew.stdlib.system: initializeSystemLib;
75         import mildew.stdlib.xmlhttprequest: initializeXMLHttpRequestLibrary;
76         initializeTypesLibrary(this);
77         initializeGlobalLibrary(this);
78         initializeBuffersLibrary(this);
79         initializeConsoleLibrary(this);
80         initializeDateLibrary(this);
81         initializeGeneratorLibrary(this);
82         initializeJSONLibrary(this);
83         initializeMapLibrary(this);
84         initializeMathLibrary(this);
85         initializeRegExpLibrary(this);
86         initializeSystemLib(this);
87         initializeXMLHttpRequestLibrary(this);
88     }
89 
90     /**
91      * This is the main entry point for evaluating a script program.
92      * Params:
93      *  code = This is the source code of a script to be executed.
94      *  program = The optional name of the program, defaults to "<program>"
95      * Returns:
96      *  If the script has a return statement with an expression, this value will be the result of that expression
97      *  otherwise it will be ScriptAny.UNDEFINED
98      */
99     ScriptAny evaluate(in string code, string name="<program>")
100     {
101         // TODO: evaluate should run all compiled chunks as a function call with module and exports
102         // parameters.
103         auto program = _compiler.compile(code, name);
104 
105         return _vm.runProgram(program, []);
106     }
107 
108     /**
109      * Evaluates a file that can be either binary bytecode or textual source code.
110      * Params:
111      *  pathName = the location of the code file in the file system.
112      * Returns:
113      *  The result of evaluating the file, undefined if no return statement.
114      */
115     ScriptAny evaluateFile(in string pathName)
116     {
117         // TODO if fromScript is true, module and exports parameter that can be checked
118         // and made the return value
119         import std.stdio: File, writefln;
120         import mildew.util.encode: decode;
121 
122         File inputFile = File(pathName, "rb");
123         auto raw = new ubyte[inputFile.size];
124         if(inputFile.size == 0)
125             return ScriptAny.UNDEFINED;
126         raw = inputFile.rawRead(raw);
127         if(raw.length > 0 && raw[0] == 0x01)
128         {
129             auto program = Program.deserialize(raw, pathName);
130             return _vm.runProgram(program, []);
131         }
132         else
133         {
134             auto source = cast(string)raw;
135             return evaluate(source, pathName);
136         }
137     }
138 
139     // TODO: Create an evaluate function with default exception handling with file name info
140 
141     /**
142      * Sets a global variable or constant without checking whether or not the variable or const was already
143      * declared. This is used by host applications to define custom functions or objects.
144      * Params:
145      *  name = The name of the global variable.
146      *  value = The value the variable should be set to.
147      *  isConst = Whether or not the script can overwrite the global.
148      */
149     void forceSetGlobal(T)(in string name, T value, bool isConst=false)
150     {
151         _globalEnvironment.forceSetVarOrConst(name, ScriptAny(value), isConst);
152     }
153 
154     /**
155      * Unsets a variable or constant in the global environment. Used by host applications to remove
156      * items that were loaded by the standard library load functions. Specific functions of
157      * script classes can be removed by modifying the "prototype" field of their constructor.
158      * Params:
159      *  name = The name of the global variable to unset
160      */
161     void forceUnsetGlobal(in string name)
162     {
163         _globalEnvironment.forceRemoveVarOrConst(name);
164     }
165 
166     /**
167      * Run the VM queued fibers. This API is still a work in progress.
168      */
169     void runVMFibers()
170     {
171         _vm.runFibersToCompletion();
172     }
173 
174     // TODO function to run one cycle of fibers once VM implements it
175 
176     /// Gets the VirtualMachine instance of this Interpreter
177     VirtualMachine vm() { return _vm; }
178 
179 private:
180 
181     // TODO a registry to keep track of loaded/run files. 
182 
183     Compiler _compiler;
184     VirtualMachine _vm;
185     Environment _globalEnvironment;
186 }
187