1 /** 2 This module implements Generators for the Mildew scripting language. 3 See https://pillager86.github.io/dmildew/Generator.html for more details. 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.stdlib.generator; 22 23 import std.concurrency; 24 25 import mildew.environment; 26 import mildew.exceptions; 27 import mildew.interpreter; 28 import mildew.types; 29 import mildew.vm; 30 31 /** 32 * The Generator class implementation. 33 */ 34 class ScriptGenerator : Generator!ScriptAny 35 { 36 /// ctor 37 this(Environment env, ScriptFunction func, ScriptAny[] args, ScriptAny thisObj = ScriptAny.UNDEFINED) 38 { 39 // first get the thisObj 40 bool _; // @suppress(dscanner.suspicious.unmodified) 41 if(thisObj == ScriptAny.UNDEFINED) 42 { 43 if(func.boundThis) 44 { 45 thisObj = func.boundThis; 46 } 47 if(func.closure && func.closure.variableOrConstExists("this")) 48 { 49 thisObj = *func.closure.lookupVariableOrConst("this", _); 50 } 51 else if(env.variableOrConstExists("this")) 52 { 53 thisObj = *env.lookupVariableOrConst("this", _); 54 } 55 // else it's undefined and that's ok 56 } 57 58 // next get a VM copy that will live in the following closure 59 if(env.g.interpreter is null) 60 throw new Exception("Global environment has null interpreter"); 61 62 auto parentVM = env.g.interpreter.vm; 63 auto childVM = parentVM.copy(); 64 65 _name = func.functionName; 66 67 super({ 68 ScriptAny[string] map; 69 map["__yield__"] = ScriptAny(new ScriptFunction("yield", &this.native_yield)); 70 map["yield"] = map["__yield__"]; 71 try 72 { 73 _returnValue = childVM.runFunction(func, thisObj, args, map); 74 } 75 catch(ScriptRuntimeException ex) 76 { 77 this._markedAsFinished = true; 78 parentVM.setException(ex); 79 } 80 }); 81 } 82 83 override string toString() const 84 { 85 return "Generator " ~ _name; 86 } 87 package: 88 89 ScriptAny native_yield(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 90 { 91 if(args.length < 1) 92 .yield!ScriptAny(ScriptAny()); 93 else 94 .yield!ScriptAny(args[0]); 95 auto result = this._yieldValue; 96 this._yieldValue = ScriptAny.UNDEFINED; 97 if(_excFlag) 98 { 99 auto exc = _excFlag; // @suppress(dscanner.suspicious.unmodified) 100 _excFlag = null; 101 throw exc; 102 } 103 return result; 104 } 105 106 private: 107 string _name; 108 bool _markedAsFinished = false; 109 ScriptRuntimeException _excFlag; 110 ScriptAny _yieldValue; 111 ScriptAny _returnValue; 112 } 113 114 /** 115 * Initializes the public Generator constructor. Generator functions are a first class language 116 * feature so this is unnecessary. See https://pillager86.github.io/dmildew/Generator.html for how 117 * to use the constructor and methods in Mildew. 118 * Params: 119 * interpreter = The Interpreter instance to load the Generator constructor into. 120 */ 121 void initializeGeneratorLibrary(Interpreter interpreter) 122 { 123 ScriptAny ctor = new ScriptFunction("Generator", &native_Generator_ctor, true); 124 ctor["prototype"] = getGeneratorPrototype(); 125 ctor["prototype"]["constructor"] = ctor; 126 interpreter.forceSetGlobal("Generator", ctor, false); 127 } 128 129 private ScriptObject _generatorPrototype; 130 131 /// Gets the Generator prototype. The VM uses this. 132 package(mildew) ScriptObject getGeneratorPrototype() 133 { 134 if(_generatorPrototype is null) 135 { 136 _generatorPrototype = new ScriptObject("Generator", null); 137 _generatorPrototype.addGetterProperty("name", new ScriptFunction("Generator.prototype.name", 138 &native_Generator_p_name)); 139 _generatorPrototype["next"] = new ScriptFunction("Generator.prototype.next", 140 &native_Generator_next); 141 _generatorPrototype["return"] = new ScriptFunction("Generator.prototype.return", 142 &native_Generator_return); 143 _generatorPrototype["throw"] = new ScriptFunction("Generator.prototype.throw", 144 &native_Generator_throw); 145 _generatorPrototype.addGetterProperty("returnValue", new ScriptFunction("Generator.prototype.returnValue", 146 &native_Generator_p_returnValue)); 147 } 148 return _generatorPrototype; 149 } 150 151 private ScriptAny native_Generator_ctor(Environment env, ScriptAny* thisObj, 152 ScriptAny[] args, ref NativeFunctionError nfe) 153 { 154 if(args.length < 1 || args[0].type != ScriptAny.Type.FUNCTION) 155 { 156 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 157 return ScriptAny("First argument to new Generator() must exist and be a Function"); 158 } 159 if(!thisObj.isObject) 160 { 161 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 162 return ScriptAny.UNDEFINED; 163 } 164 auto obj = thisObj.toValue!ScriptObject; 165 obj.nativeObject = new ScriptGenerator(env, args[0].toValue!ScriptFunction, args[1..$]); 166 return ScriptAny.UNDEFINED; 167 } 168 169 private ScriptAny native_Generator_p_name(Environment env, ScriptAny* thisObj, 170 ScriptAny[] args, ref NativeFunctionError nfe) 171 { 172 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 173 if(thisGen is null) 174 { 175 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 176 return ScriptAny.UNDEFINED; 177 } 178 return ScriptAny(thisGen._name); 179 } 180 181 /// The virtual machine uses this 182 package(mildew) ScriptAny native_Generator_next(Environment env, ScriptAny* thisObj, 183 ScriptAny[] args, ref NativeFunctionError nfe) 184 { 185 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 186 if(thisGen is null) 187 { 188 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 189 return ScriptAny.UNDEFINED; 190 } 191 auto valueToYield = args.length > 0 ? args[0] : ScriptAny.UNDEFINED; // @suppress(dscanner.suspicious.unmodified) 192 thisGen._yieldValue = valueToYield; 193 194 if(thisGen._markedAsFinished) 195 { 196 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 197 return ScriptAny("Cannot call next on a finished Generator"); 198 } 199 if(!thisGen.empty) 200 { 201 auto obj = new ScriptObject("iteration", null); 202 obj["done"] = ScriptAny(false); 203 obj["value"] = thisGen.front(); 204 try 205 { 206 thisGen.popFront(); 207 } 208 catch(ScriptRuntimeException ex) 209 { 210 thisGen._markedAsFinished = true; 211 return ScriptAny.UNDEFINED; 212 } 213 return ScriptAny(obj); 214 } 215 else 216 { 217 thisGen._markedAsFinished = true; 218 auto obj = new ScriptObject("iteration", null); 219 obj["done"] = ScriptAny(true); 220 return ScriptAny(obj); 221 } 222 } 223 224 private ScriptAny native_Generator_return(Environment env, ScriptAny* thisObj, 225 ScriptAny[] args, ref NativeFunctionError nfe) 226 { 227 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 228 if(thisGen is null) 229 { 230 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 231 return ScriptAny.UNDEFINED; 232 } 233 ScriptAny retVal = args.length > 0 ? args[0] : ScriptAny.UNDEFINED; // @suppress(dscanner.suspicious.unmodified) 234 if(thisGen._markedAsFinished) 235 { 236 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 237 return ScriptAny("Cannot call return on a finished Generator"); 238 } 239 while(!thisGen.empty) 240 { 241 try 242 { 243 thisGen.popFront(); 244 } 245 catch(ScriptRuntimeException ex) 246 { 247 thisGen._markedAsFinished = true; 248 return ScriptAny.UNDEFINED; 249 } 250 } 251 thisGen._markedAsFinished = true; 252 auto obj = new ScriptObject("iteration", null); 253 obj["value"] = retVal; 254 obj["done"] = ScriptAny(true); 255 return ScriptAny(obj); 256 } 257 258 private ScriptAny native_Generator_throw(Environment env, ScriptAny* thisObj, 259 ScriptAny[] args, ref NativeFunctionError nfe) 260 { 261 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 262 if(thisGen is null) 263 { 264 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 265 return ScriptAny.UNDEFINED; 266 } 267 if(args.length < 1) 268 { 269 nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS; 270 return ScriptAny.UNDEFINED; 271 } 272 if(thisGen._markedAsFinished) 273 { 274 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 275 return ScriptAny("Cannot call throw on a finished Generator"); 276 } 277 auto exc = new ScriptRuntimeException("Generator exception"); 278 exc.thrownValue = args[0]; 279 thisGen._excFlag = exc; 280 auto result = native_Generator_next(env, thisObj, [], nfe); 281 return result; 282 } 283 284 private ScriptAny native_Generator_p_returnValue(Environment env, ScriptAny* thisObj, 285 ScriptAny[] args, ref NativeFunctionError nfe) 286 { 287 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 288 if(thisGen is null) 289 { 290 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 291 return ScriptAny.UNDEFINED; 292 } 293 return thisGen._returnValue; 294 } 295