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 alias opCmp = ScriptObject.opCmp; 149 int opCmp(const ScriptFunction other) const 150 { 151 if(_type != other._type) 152 return cast(int)_type - cast(int)other._type; 153 154 if(_functionName < other._functionName) 155 return -1; 156 else if(_functionName > other._functionName) 157 return 1; 158 159 if(_isClass && !other._isClass) 160 return 1; 161 else if(!_isClass && other._isClass) 162 return -1; 163 164 if(_type == Type.SCRIPT_FUNCTION) 165 { 166 if(_compiled.length != other._compiled.length) 167 { 168 if(_compiled.length > other._compiled.length) 169 return 1; 170 else if(_compiled.length < other._compiled.length) 171 return -1; 172 } 173 else if(_compiled > other._compiled) 174 return 1; 175 else if(_compiled < other._compiled) 176 return -1; 177 else if(_compiled.length == 0) 178 { 179 if(_argNames > other._argNames) 180 return 1; 181 else if(_argNames < other._argNames) 182 return -1; 183 184 if(_statementNodes > other._statementNodes) 185 return 1; 186 else if(_statementNodes < other._statementNodes) 187 return -1; 188 } 189 } 190 else if(_type == Type.NATIVE_DELEGATE) 191 { 192 if(_nativeDelegate > other._nativeDelegate) 193 return 1; 194 else if(_nativeDelegate < other._nativeDelegate) 195 return -1; 196 } 197 else if(_type == Type.NATIVE_FUNCTION) 198 { 199 if(_nativeFunction > other._nativeFunction) 200 return 1; 201 else if(_nativeFunction < other._nativeFunction) 202 return -1; 203 } 204 return 0; 205 } 206 207 override size_t toHash() const @safe nothrow 208 { 209 if(_compiled.length > 0) 210 { 211 return typeid(_compiled).getHash(&_compiled); 212 } 213 // lacking but not sure what else to do 214 return typeid(_functionName).getHash(&_functionName); 215 } 216 217 alias opEquals = ScriptObject.opEquals; 218 bool opEquals(ScriptFunction other) const 219 { 220 return opCmp(other) == 0; 221 } 222 223 /// ct property 224 ConstTable constTable() { return _constTable; } 225 /// ct property 226 ConstTable constTable(ConstTable ct) { return _constTable = ct; } 227 228 package(mildew): 229 230 /** 231 * Constructor for functions created from compilation of statements. 232 */ 233 this(string fnname, string[] args, ubyte[] bc, bool isClass = false, bool isGenerator = false, ConstTable ct = null) 234 { 235 import mildew.types.bindings: getFunctionPrototype; 236 immutable tname = isClass? "class" : "function"; 237 super(tname, getFunctionPrototype(), null); 238 _functionName = fnname; 239 _argNames = args; 240 _compiled = bc; 241 _isClass = isClass; 242 _isGenerator = isGenerator; 243 _constTable = ct; 244 initializePrototypeProperty(); 245 _type = Type.SCRIPT_FUNCTION; 246 } 247 248 /** 249 * Method to copy fresh compiled functions with the correct environment 250 */ 251 ScriptFunction copyCompiled(Environment env) 252 { 253 auto newFunc = new ScriptFunction(_functionName, _argNames, _compiled, _isClass, _isGenerator, _constTable); 254 newFunc._closure = env; 255 return newFunc; 256 } 257 258 // must check type before using these properties or one gets an exception 259 260 /// get the native function ONLY if it is one 261 auto nativeFunction() 262 { 263 if(_type == Type.NATIVE_FUNCTION) 264 return _nativeFunction; 265 else 266 throw new Exception("This is not a native function"); 267 } 268 269 /// get the delegate only if it is one 270 auto nativeDelegate() 271 { 272 if(_type == Type.NATIVE_DELEGATE) 273 return _nativeDelegate; 274 else 275 throw new Exception("This is not a native delegate"); 276 } 277 278 /// Sets the function name 279 auto functionName(in string fnName) { return _functionName = fnName; } 280 281 /// Property statementNodes 282 auto statementNodes() { return _statementNodes; } 283 284 /// Property get closure 285 auto closure() { return _closure; } 286 /// Property set closure 287 auto closure(Environment c) { return _closure = c; } 288 289 /// Property isClass 290 auto isClass() const { return _isClass; } 291 292 /// used by the parser for missing constructors in classes that don't extend 293 static deprecated ScriptFunction emptyFunction(in string name, bool isClass) 294 { 295 return new ScriptFunction(name, &native_EMPTY_FUNCTION, isClass); 296 } 297 298 private: 299 Type _type; 300 string _functionName; 301 string[] _argNames; 302 StatementNode[] _statementNodes; 303 ScriptAny _boundThis; 304 Environment _closure = null; 305 bool _isClass = false; 306 bool _isGenerator = false; 307 ConstTable _constTable; 308 union 309 { 310 NativeFunction _nativeFunction; 311 NativeDelegate _nativeDelegate; 312 } 313 314 void initializePrototypeProperty() 315 { 316 _dictionary["prototype"] = ScriptAny(new ScriptObject("Object", null)); 317 _dictionary["prototype"]["constructor"] = ScriptAny(this); 318 } 319 320 ubyte[] _compiled; 321 322 } 323 324 deprecated private ScriptAny native_EMPTY_FUNCTION(Environment e, ScriptAny* thisObj, ScriptAny[] args, 325 ref NativeFunctionError nfe) 326 { 327 return ScriptAny.UNDEFINED; 328 } 329