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