1 /**
2 This module implements the ScriptFunction class, which holds script defined functions as well as native D
3 functions or delegates with the correct signature.
4 
5 ────────────────────────────────────────────────────────────────────────────────
6 
7 Copyright (C) 2021 pillager86.rf.gd
8 
9 This program is free software: you can redistribute it and/or modify it under 
10 the terms of the GNU General Public License as published by the Free Software 
11 Foundation, either version 3 of the License, or (at your option) any later 
12 version.
13 
14 This program is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License along with 
19 this program.  If not, see <https://www.gnu.org/licenses/>.
20 */
21 module mildew.types.func;
22 
23 import mildew.environment: Environment;
24 import mildew.compiler;
25 import mildew.types.any: ScriptAny;
26 import mildew.types.object: ScriptObject;
27 import mildew.vm;
28 
29 /** 
30  * When a native function or delegate encounters an error with the arguments sent,
31  * the last reference parameter should be set to the appropriate enum value.
32  * A specific exception can be thrown by setting the flag to RETURN_VALUE_IS_EXCEPTION and
33  * returning a string.
34  * If an exception is thrown directly inside a native function, the user will not be able to
35  * see a traceback of the script source code lines where the error occurred.
36  * Note: with the redesign of the virtual machine, native bindings can now directly throw
37  * a ScriptRuntimeException as long as the native function is called from a script.
38  */
39 enum NativeFunctionError 
40 {
41     NO_ERROR = 0,
42     WRONG_NUMBER_OF_ARGS,
43     WRONG_TYPE_OF_ARG,
44     RETURN_VALUE_IS_EXCEPTION
45 }
46 
47 /// native function signature to be usable by scripting language
48 alias NativeFunction = ScriptAny function(Environment, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError);
49 /// native delegate signature to be usable by scripting language
50 alias NativeDelegate = ScriptAny delegate(Environment, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError);
51 
52 /**
53  * This class encapsulates all types of script functions including native D functions and delegates. A
54  * native function must first be wrapped in this class before it can be given to a ScriptAny assignment.
55  * When an object is created with "new FunctionName()" its __proto__ is assigned to the function's "prototype"
56  * field. This allows OOP in the scripting language and is analogous to JavaScript.
57  */
58 class ScriptFunction : ScriptObject
59 {
60     import mildew.nodes: StatementNode;
61     import mildew.interpreter: Interpreter;
62 public:
63     /// The type of function held by the object
64     enum Type { SCRIPT_FUNCTION, NATIVE_FUNCTION, NATIVE_DELEGATE }
65 
66     /**
67      * Constructs a new ScriptFunction out of a native D function.
68      * Params:
69      *  fname = The name of the function.
70      *  nfunc = The address of the native function. See NativeFunction alias for correct signature
71      *  isClass = Whether or not this function is a constructor. This information is used when printing
72      */
73     this(string fname, NativeFunction nfunc, bool isClass = false)
74     {
75         immutable tname = isClass? "class" : "function";
76         import mildew.types.bindings: getFunctionPrototype;
77         super(tname, getFunctionPrototype, null);
78         _functionName = fname;
79 		_isClass = isClass;
80         initializePrototypeProperty();
81         _type = Type.NATIVE_FUNCTION;
82         _nativeFunction = nfunc;
83     }
84 
85     /**
86      * Constructs a new ScriptFunction out of a native D delegate.
87      * Params:
88      *  fname = The name of the function.
89      *  nfunc = The address of the native delegate. See NativeDelegate alias for correct signature
90      *  isClass = Whether or not this function is a constructor. This information is used when printing
91      */
92     this(string fname, NativeDelegate ndele, bool isClass = false)
93     {
94         immutable tname = isClass? "class" : "function";
95         import mildew.types.bindings: getFunctionPrototype;
96         super(tname, getFunctionPrototype, null);
97         _functionName = fname;
98 		_isClass = isClass;
99         initializePrototypeProperty();
100         _type = Type.NATIVE_DELEGATE;
101         _nativeDelegate = ndele;
102     }
103 
104     /**
105      * Check if an object is an instance of a "class" constructor
106      */
107     static bool isInstanceOf(ScriptObject obj, ScriptFunction clazz)
108     {
109         if(obj is null || clazz is null)
110             return false;
111         auto proto = obj.prototype;
112         while(proto !is null)
113         {
114             if(proto["constructor"].toValue!ScriptFunction is clazz)
115                 return true;
116             proto = proto.prototype;
117         }
118         return false;
119     }
120 
121     /**
122      * Binds a specific "this" to be used no matter what. Internal users of ScriptFunction such as
123      * mildew.vm.virtualmachine.VirtualMachine must manually check the boundThis property and set this up.
124      * Unbinding is done by passing UNDEFINED as the parameter.
125      */
126     void bind(ScriptAny thisObj)
127     {
128         _boundThis = thisObj;
129     }
130 
131     /// Returns a string representing the type and name.
132     override string toString() const
133     {
134         return name ~ " " ~ _functionName;
135     }
136 
137     /// Returns the type of function stored, such as native function, delegate, or script function
138     auto type() const { return _type; }
139     /// Returns the name of the function
140     auto functionName() const { return _functionName; }
141     /// Property argNames. Note: native functions do not have this.
142     auto argNames() const { return _argNames; }
143     /// Compiled form (raw ubyte array)
144     ubyte[] compiled() { return _compiled; }
145     /// bound this property. change with bind()
146     ScriptAny boundThis() { return _boundThis; }
147     /// isGenerator property. used by various pieces
148     bool isGenerator() const { return _isGenerator; }
149 
150     alias opCmp = ScriptObject.opCmp;
151     /// Compares two ScriptFunctions
152     int opCmp(const ScriptFunction other) const
153     {
154         if(_type != other._type)
155             return cast(int)_type - cast(int)other._type;
156 
157         if(_functionName < other._functionName)
158             return -1;
159         else if(_functionName > other._functionName)
160             return 1;
161 
162         if(_isClass && !other._isClass)
163             return 1;
164         else if(!_isClass && other._isClass)
165             return -1;
166         
167         if(_type == Type.SCRIPT_FUNCTION)
168         {
169             if(_compiled.length != other._compiled.length)
170             {
171                 if(_compiled.length > other._compiled.length)
172                     return 1;
173                 else if(_compiled.length < other._compiled.length)
174                     return -1;
175             }
176             else if(_compiled > other._compiled)
177                 return 1;
178             else if(_compiled < other._compiled)
179                 return -1;
180             else if(_compiled.length == 0)
181             {
182                 if(_argNames > other._argNames)
183                     return 1;
184                 else if(_argNames < other._argNames)
185                     return -1;
186 
187                 if(_statementNodes > other._statementNodes)
188                     return 1;
189                 else if(_statementNodes < other._statementNodes)
190                     return -1;
191             }
192         }
193         else if(_type == Type.NATIVE_DELEGATE)
194         {
195             if(_nativeDelegate > other._nativeDelegate)
196                 return 1;
197             else if(_nativeDelegate < other._nativeDelegate)
198                 return -1;
199         }
200         else if(_type == Type.NATIVE_FUNCTION)
201         {
202             if(_nativeFunction > other._nativeFunction)
203                 return 1;
204             else if(_nativeFunction < other._nativeFunction)
205                 return -1;
206         }
207         return 0;
208     }
209 
210     /// Generates a hash from a ScriptFunction
211     override size_t toHash() const @trusted nothrow
212     {
213         if(_type == Type.SCRIPT_FUNCTION)
214             return typeid(_compiled).getHash(&_compiled);
215         else if(_type == Type.NATIVE_FUNCTION)
216             return typeid(_nativeFunction).getHash(&_nativeFunction);
217         else if(_type == Type.NATIVE_DELEGATE)
218             return typeid(_nativeDelegate).getHash(&_nativeDelegate);
219         // unreachable code
220         return typeid(_functionName).getHash(&_functionName);
221     }
222 
223     alias opEquals = ScriptObject.opEquals;
224     /// Tests two ScriptFunctions for equality
225     bool opEquals(ScriptFunction other) const
226     {
227         if(_type != other._type || _functionName != other._functionName)
228             return false;
229         if(_type == Type.SCRIPT_FUNCTION)
230             return _compiled == other._compiled;
231         else if(_type == Type.NATIVE_FUNCTION)
232             return _nativeFunction == other._nativeFunction;
233         else if(_type == Type.NATIVE_DELEGATE)
234             return _nativeDelegate == other._nativeDelegate;
235         return false;
236     }
237 
238     /// ct property (currently null and unused)
239     ConstTable constTable() { return _constTable; }
240     /// ct property (settable but does nothing yet)
241     ConstTable constTable(ConstTable ct) { return _constTable = ct; }
242 
243 package(mildew):
244 
245     /**
246      * Constructor for functions created from compilation of statements.
247      */
248     this(string fnname, string[] args, ubyte[] bc, bool isClass = false, bool isGenerator = false, ConstTable ct = null)
249     {
250         import mildew.types.bindings: getFunctionPrototype;
251         immutable tname = isClass? "class" : "function";
252         super(tname, getFunctionPrototype(), null);
253         _functionName = fnname;
254         _argNames = args;
255         _compiled = bc;
256         _isClass = isClass;
257         _isGenerator = isGenerator;
258         _constTable = ct;
259         initializePrototypeProperty();
260         _type = Type.SCRIPT_FUNCTION;
261     }
262 
263     /**
264      * Method to copy fresh compiled functions with the correct environment
265      */
266     ScriptFunction copyCompiled(Environment env)
267     {
268         auto newFunc = new ScriptFunction(_functionName, _argNames, _compiled, _isClass, _isGenerator, _constTable);
269         newFunc._closure = env;
270         return newFunc;
271     }
272 
273     /**
274      * Generic copying for all functions
275      */
276     ScriptFunction copy(Environment env)
277     {
278         if(_type == ScriptFunction.Type.SCRIPT_FUNCTION)
279             return copyCompiled(env);
280         else if(_type == ScriptFunction.Type.NATIVE_FUNCTION)
281             return new ScriptFunction(_functionName, _nativeFunction, _isClass);
282         else if(_type == ScriptFunction.Type.NATIVE_DELEGATE)
283             return new ScriptFunction(_functionName, _nativeDelegate, _isClass);
284         else
285             throw new Exception("Impossible ScriptFunction type");
286     }
287 
288     // must check type before using these properties or one gets an exception
289 
290     /// get the native function ONLY if it is one
291     auto nativeFunction()
292     {
293         if(_type == Type.NATIVE_FUNCTION)
294             return _nativeFunction;
295         else
296             throw new Exception("This is not a native function");
297     }
298 
299     /// get the delegate only if it is one
300     auto nativeDelegate()
301     {
302         if(_type == Type.NATIVE_DELEGATE)
303             return _nativeDelegate;
304         else
305             throw new Exception("This is not a native delegate");
306     }
307 
308     /// Sets the function name
309     auto functionName(in string fnName) { return _functionName = fnName; }
310 
311     /// Property statementNodes
312     auto statementNodes() { return _statementNodes; }
313 
314 	/// Property get closure
315 	auto closure() { return _closure; }
316     /// Property set closure
317     auto closure(Environment c) { return _closure = c; }
318 
319     /// Property isClass
320     auto isClass() const { return _isClass; }
321 
322 private:
323     Type _type;
324     string _functionName;
325     string[] _argNames;
326     StatementNode[] _statementNodes;
327     ScriptAny _boundThis;
328 	Environment _closure = null;
329 	bool _isClass = false;
330     bool _isGenerator = false;
331     ConstTable _constTable;
332     union 
333     {
334         NativeFunction _nativeFunction;
335         NativeDelegate _nativeDelegate;
336     }
337 
338     void initializePrototypeProperty()
339     {
340         _dictionary["prototype"] = ScriptAny(new ScriptObject("Object", null));
341         _dictionary["prototype"]["constructor"] = ScriptAny(this);
342     }
343 
344     ubyte[] _compiled;
345 
346 }
347