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