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  */
37 enum NativeFunctionError 
38 {
39     NO_ERROR = 0,
40     WRONG_NUMBER_OF_ARGS,
41     WRONG_TYPE_OF_ARG,
42     RETURN_VALUE_IS_EXCEPTION
43 }
44 
45 /// native function signature to be usable by scripting language
46 alias NativeFunction = ScriptAny function(Environment, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError);
47 /// native delegate signature to be usable by scripting language
48 alias NativeDelegate = ScriptAny delegate(Environment, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError);
49 
50 /**
51  * This class encapsulates all types of script functions including native D functions and delegates. A
52  * native function must first be wrapped in this class before it can be given to a ScriptAny assignment.
53  * When an object is created with "new FunctionName()" its __proto__ is assigned to the function's "prototype"
54  * field. This allows OOP in the scripting language and is analogous to JavaScript.
55  */
56 class ScriptFunction : ScriptObject
57 {
58     import mildew.nodes: StatementNode;
59     import mildew.interpreter: Interpreter;
60 public:
61     /// The type of function held by the object
62     enum Type { SCRIPT_FUNCTION, NATIVE_FUNCTION, NATIVE_DELEGATE }
63 
64     /**
65      * Constructs a new ScriptFunction out of a native D function.
66      * Params:
67      *  fname = The name of the function.
68      *  nfunc = The address of the native function. See NativeFunction alias for correct signature
69      *  isClass = Whether or not this function is a constructor. This information is used when printing
70      */
71     this(string fname, NativeFunction nfunc, bool isClass = false)
72     {
73         immutable tname = isClass? "class" : "function";
74         import mildew.types.bindings: getFunctionPrototype;
75         super(tname, getFunctionPrototype, null);
76         _functionName = fname;
77 		_isClass = isClass;
78         initializePrototypeProperty();
79         _type = Type.NATIVE_FUNCTION;
80         _nativeFunction = nfunc;
81     }
82 
83     /**
84      * Constructs a new ScriptFunction out of a native D delegate.
85      * Params:
86      *  fname = The name of the function.
87      *  nfunc = The address of the native delegate. See NativeDelegate alias for correct signature
88      *  isClass = Whether or not this function is a constructor. This information is used when printing
89      */
90     this(string fname, NativeDelegate ndele, bool isClass = false)
91     {
92         immutable tname = isClass? "class" : "function";
93         import mildew.types.bindings: getFunctionPrototype;
94         super(tname, getFunctionPrototype, null);
95         _functionName = fname;
96 		_isClass = isClass;
97         initializePrototypeProperty();
98         _type = Type.NATIVE_DELEGATE;
99         _nativeDelegate = ndele;
100     }
101 
102     /**
103      * Check if an object is an instance of a "class" constructor
104      */
105     static bool isInstanceOf(ScriptObject obj, ScriptFunction clazz)
106     {
107         if(obj is null || clazz is null)
108             return false;
109         auto proto = obj.prototype;
110         while(proto !is null)
111         {
112             if(proto["constructor"].toValue!ScriptFunction is clazz)
113                 return true;
114             proto = proto.prototype;
115         }
116         return false;
117     }
118 
119     /**
120      * Binds a specific this to be used no matter what. Internal users of ScriptFunction such as
121      * mildew.vm.virtualmachine.VirtualMachine must manually check the boundThis property and set this up.
122      * Unbinding is done by passing UNDEFINED as the parameter.
123      */
124     void bind(ScriptAny thisObj)
125     {
126         _boundThis = thisObj;
127     }
128 
129     /// Returns a string representing the type and name.
130     override string toString() const
131     {
132         return name ~ " " ~ _functionName;
133     }
134 
135     /// Returns the type of function stored, such as native function, delegate, or script function
136     auto type() const { return _type; }
137     /// Returns the name of the function
138     auto functionName() const { return _functionName; }
139     /// Property argNames. Note: native functions do not have this.
140     auto argNames() { return _argNames; }
141     /// Compiled form cached
142     ubyte[] compiled() { return _compiled; }
143     /// bound this property. change with bind()
144     ScriptAny boundThis() { return _boundThis; }
145     /// isGenerator property. used by various pieces
146     bool isGenerator() const { return _isGenerator; }
147 
148     int opCmp(const ScriptFunction other) const
149     {
150         if(_type != other._type)
151             return cast(int)_type - cast(int)other._type;
152 
153         if(_functionName < other._functionName)
154             return -1;
155         else if(_functionName > other._functionName)
156             return 1;
157 
158         if(_isClass && !other._isClass)
159             return 1;
160         else if(!_isClass && other._isClass)
161             return -1;
162         
163         if(_type == Type.SCRIPT_FUNCTION)
164         {
165             if(_compiled.length != other._compiled.length)
166             {
167                 if(_compiled.length > other._compiled.length)
168                     return 1;
169                 else if(_compiled.length < other._compiled.length)
170                     return -1;
171             }
172             else if(_compiled > other._compiled)
173                 return 1;
174             else if(_compiled < other._compiled)
175                 return -1;
176             else if(_compiled.length == 0)
177             {
178                 if(_argNames > other._argNames)
179                     return 1;
180                 else if(_argNames < other._argNames)
181                     return -1;
182 
183                 if(_statementNodes > other._statementNodes)
184                     return 1;
185                 else if(_statementNodes < other._statementNodes)
186                     return -1;
187             }
188         }
189         else if(_type == Type.NATIVE_DELEGATE)
190         {
191             if(_nativeDelegate > other._nativeDelegate)
192                 return 1;
193             else if(_nativeDelegate < other._nativeDelegate)
194                 return -1;
195         }
196         else if(_type == Type.NATIVE_FUNCTION)
197         {
198             if(_nativeFunction > other._nativeFunction)
199                 return 1;
200             else if(_nativeFunction < other._nativeFunction)
201                 return -1;
202         }
203         return 0;
204     }
205 
206     override size_t toHash() const @safe nothrow
207     {
208         if(_compiled.length > 0)
209         {
210             return typeid(_compiled).getHash(&_compiled);
211         }
212         // lacking but not sure what else to do
213         return typeid(_functionName).getHash(&_functionName);
214     }
215 
216     bool opEquals(ScriptFunction other) const
217     {
218         return opCmp(other) == 0;
219     }
220 
221 package(mildew):
222 
223     /**
224      * Constructor for creating script defined functions.
225      */
226     deprecated this(string fnname, string[] args, StatementNode[] statementNodes, Environment clos, 
227             bool isClass=false)
228     {
229         import mildew.types.bindings: getFunctionPrototype;
230         immutable tname = isClass? "class" : "function";
231         super(tname, getFunctionPrototype(), null);
232         _functionName = fnname;
233         _argNames = args;
234         _statementNodes = statementNodes;
235 		_closure = clos;
236 		_isClass = isClass;
237         initializePrototypeProperty();
238         _type = Type.SCRIPT_FUNCTION;
239     }
240 
241     /**
242      * Constructor for functions created from compilation of statements.
243      */
244     this(string fnname, string[] args, ubyte[] bc, bool isClass = false, bool isGenerator = false)
245     {
246         import mildew.types.bindings: getFunctionPrototype;
247         immutable tname = isClass? "class" : "function";
248         super(tname, getFunctionPrototype(), null);
249         _functionName = fnname;
250         _argNames = args;
251         _compiled = bc;
252         _isClass = isClass;
253         _isGenerator = isGenerator;
254         initializePrototypeProperty();
255         _type = Type.SCRIPT_FUNCTION;
256     }
257 
258     /**
259      * Method to copy fresh compiled functions with the correct environment
260      */
261     ScriptFunction copyCompiled(Environment env, bool isClass=false)
262     {
263         auto newFunc = new ScriptFunction(_functionName, _argNames, _compiled, isClass, _isGenerator);
264         newFunc._closure = env;
265         return newFunc;
266     }
267 
268     // must check type before using these properties or one gets an exception
269 
270     /// get the native function ONLY if it is one
271     auto nativeFunction()
272     {
273         if(_type == Type.NATIVE_FUNCTION)
274             return _nativeFunction;
275         else
276             throw new Exception("This is not a native function");
277     }
278 
279     /// get the delegate only if it is one
280     auto nativeDelegate()
281     {
282         if(_type == Type.NATIVE_DELEGATE)
283             return _nativeDelegate;
284         else
285             throw new Exception("This is not a native delegate");
286     }
287 
288     /// Sets the function name
289     auto functionName(in string fnName) { return _functionName = fnName; }
290 
291     /// Property statementNodes
292     auto statementNodes() { return _statementNodes; }
293 
294 	/// Property get closure
295 	auto closure() { return _closure; }
296 
297     /// Property isClass
298     auto isClass() const { return _isClass; }
299 
300     /// used by the parser for missing constructors in classes that don't extend
301     static deprecated ScriptFunction emptyFunction(in string name, bool isClass)
302     {
303         return new ScriptFunction(name, &native_EMPTY_FUNCTION, isClass);
304     }
305 
306 private:
307     Type _type;
308     string _functionName;
309     string[] _argNames;
310     StatementNode[] _statementNodes;
311     ScriptAny _boundThis;
312 	Environment _closure = null;
313 	bool _isClass = false;
314     bool _isGenerator = false;
315     union {
316         NativeFunction _nativeFunction;
317         NativeDelegate _nativeDelegate;
318     }
319 
320     void initializePrototypeProperty()
321     {
322         _dictionary["prototype"] = ScriptAny(new ScriptObject("Object", null));
323         _dictionary["prototype"]["constructor"] = ScriptAny(this);
324     }
325 
326     ubyte[] _compiled;
327 
328 }
329 
330 deprecated private ScriptAny native_EMPTY_FUNCTION(Environment e, ScriptAny* thisObj, ScriptAny[] args, 
331                                         ref NativeFunctionError nfe)
332 {
333     return ScriptAny.UNDEFINED;
334 }
335