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 /// Generator class 32 class ScriptGenerator : Generator!ScriptAny 33 { 34 /// ctor 35 this(Environment env, ScriptFunction func, ScriptAny[] args, ScriptAny thisObj = ScriptAny.UNDEFINED) 36 { 37 // first get the thisObj 38 bool _; // @suppress(dscanner.suspicious.unmodified) 39 if(thisObj == ScriptAny.UNDEFINED) 40 { 41 if(func.boundThis) 42 { 43 thisObj = func.boundThis; 44 } 45 if(func.closure && func.closure.variableOrConstExists("this")) 46 { 47 thisObj = *func.closure.lookupVariableOrConst("this", _); 48 } 49 else if(env.variableOrConstExists("this")) 50 { 51 thisObj = *env.lookupVariableOrConst("this", _); 52 } 53 // else it's undefined and that's ok 54 } 55 56 // next get a VM copy that will live in the following closure 57 if(env.g.interpreter is null) 58 throw new Exception("Global environment has null interpreter"); 59 auto vm = env.g.interpreter.vm.copy(); 60 61 _name = func.functionName; 62 63 super({ 64 _returnValue = vm.runFunction(func, thisObj, args, ScriptAny(new ScriptFunction("yield", 65 &this.native_yield))); 66 }); 67 } 68 69 override string toString() const 70 { 71 return "Generator " ~ _name; 72 } 73 package: 74 75 ScriptAny native_yield(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 76 { 77 if(args.length < 1) 78 .yield!ScriptAny(ScriptAny()); 79 else 80 .yield!ScriptAny(args[0]); 81 auto result = this._yieldValue; 82 this._yieldValue = ScriptAny.UNDEFINED; 83 return result; 84 } 85 86 private: 87 string _name; 88 bool _markedAsFinished = false; 89 ScriptAny _yieldValue; 90 ScriptAny _returnValue; 91 } 92 93 /** 94 * Initializes the public Generator constructor. Generator functions are a first class language 95 * feature so this is unnecessary. See https://pillager86.github.io/dmildew/Generator.html for how 96 * to use the constructor and methods in Mildew. 97 */ 98 void initializeGeneratorLibrary(Interpreter interpreter) 99 { 100 ScriptAny ctor = new ScriptFunction("Generator", &native_Generator_ctor, true); 101 ctor["prototype"] = getGeneratorPrototype(); 102 ctor["prototype"]["constructor"] = ctor; 103 interpreter.forceSetGlobal("Generator", ctor, false); 104 } 105 106 private ScriptObject _generatorPrototype; 107 108 /// Gets the Generator prototype. The VM will eventually need this. 109 ScriptObject getGeneratorPrototype() 110 { 111 if(_generatorPrototype is null) 112 { 113 _generatorPrototype = new ScriptObject("Generator", null); 114 _generatorPrototype.addGetterProperty("name", new ScriptFunction("Generator.prototype.name", 115 &native_Generator_p_name)); 116 _generatorPrototype["next"] = new ScriptFunction("Generator.prototype.next", 117 &native_Generator_next); 118 _generatorPrototype["return"] = new ScriptFunction("Generator.prototype.return", 119 &native_Generator_return); 120 _generatorPrototype.addGetterProperty("returnValue", new ScriptFunction("Generator.prototype.returnValue", 121 &native_Generator_p_returnValue)); 122 } 123 return _generatorPrototype; 124 } 125 126 private ScriptAny native_Generator_ctor(Environment env, ScriptAny* thisObj, 127 ScriptAny[] args, ref NativeFunctionError nfe) 128 { 129 if(args.length < 1 || args[0].type != ScriptAny.Type.FUNCTION) 130 { 131 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 132 return ScriptAny("First argument to new Generator() must exist and be a Function"); 133 } 134 if(!thisObj.isObject) 135 { 136 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 137 return ScriptAny.UNDEFINED; 138 } 139 auto obj = thisObj.toValue!ScriptObject; 140 obj.nativeObject = new ScriptGenerator(env, args[0].toValue!ScriptFunction, args[1..$]); 141 return ScriptAny.UNDEFINED; 142 } 143 144 private ScriptAny native_Generator_p_name(Environment env, ScriptAny* thisObj, 145 ScriptAny[] args, ref NativeFunctionError nfe) 146 { 147 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 148 if(thisGen is null) 149 { 150 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 151 return ScriptAny.UNDEFINED; 152 } 153 return ScriptAny(thisGen._name); 154 } 155 156 /// This is public for opIter 157 ScriptAny native_Generator_next(Environment env, ScriptAny* thisObj, 158 ScriptAny[] args, ref NativeFunctionError nfe) 159 { 160 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 161 if(thisGen is null) 162 { 163 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 164 return ScriptAny.UNDEFINED; 165 } 166 auto valueToYield = args.length > 0 ? args[0] : ScriptAny.UNDEFINED; // @suppress(dscanner.suspicious.unmodified) 167 thisGen._yieldValue = valueToYield; 168 169 if(thisGen._markedAsFinished) 170 { 171 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 172 return ScriptAny("Cannot call next on a finished Generator"); 173 } 174 if(!thisGen.empty) 175 { 176 auto obj = new ScriptObject("iteration", null); 177 obj["done"] = ScriptAny(false); 178 obj["value"] = thisGen.front(); 179 try 180 { 181 thisGen.popFront(); 182 } 183 catch(ScriptRuntimeException ex) 184 { 185 thisGen._markedAsFinished = true; 186 return ScriptAny.UNDEFINED; 187 } 188 return ScriptAny(obj); 189 } 190 else 191 { 192 thisGen._markedAsFinished = true; 193 auto obj = new ScriptObject("iteration", null); 194 obj["done"] = ScriptAny(true); 195 return ScriptAny(obj); 196 } 197 } 198 199 private ScriptAny native_Generator_return(Environment env, ScriptAny* thisObj, 200 ScriptAny[] args, ref NativeFunctionError nfe) 201 { 202 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 203 if(thisGen is null) 204 { 205 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 206 return ScriptAny.UNDEFINED; 207 } 208 ScriptAny retVal = args.length > 0 ? args[0] : ScriptAny.UNDEFINED; // @suppress(dscanner.suspicious.unmodified) 209 if(thisGen._markedAsFinished) 210 { 211 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 212 return ScriptAny("Cannot call return on a finished Generator"); 213 } 214 while(!thisGen.empty) 215 { 216 try 217 { 218 thisGen.popFront(); 219 } 220 catch(ScriptRuntimeException ex) 221 { 222 thisGen._markedAsFinished = true; 223 return ScriptAny.UNDEFINED; 224 } 225 } 226 thisGen._markedAsFinished = true; 227 auto obj = new ScriptObject("iteration", null); 228 obj["value"] = retVal; 229 obj["done"] = ScriptAny(true); 230 return ScriptAny(obj); 231 } 232 233 // we are not adding throw I've ripped up the Lexer enough as it is 234 235 private ScriptAny native_Generator_p_returnValue(Environment env, ScriptAny* thisObj, 236 ScriptAny[] args, ref NativeFunctionError nfe) 237 { 238 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 239 if(thisGen is null) 240 { 241 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 242 return ScriptAny.UNDEFINED; 243 } 244 return thisGen._returnValue; 245 } 246