1 /** 2 This module implements Generators for the Mildew scripting language. 3 4 ──────────────────────────────────────────────────────────────────────────────── 5 6 Copyright (C) 2021 pillager86.rf.gd 7 8 This program is free software: you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation, either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT ANY 14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 15 PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License along with 18 this program. If not, see <https://www.gnu.org/licenses/>. 19 */ 20 module mildew.stdlib.generator; 21 22 import std.concurrency; 23 24 import mildew.environment; 25 import mildew.exceptions; 26 import mildew.interpreter; 27 import mildew.types; 28 import mildew.vm; 29 30 /// Generator class 31 class ScriptGenerator : Generator!ScriptAny 32 { 33 /// ctor 34 this(Environment env, ScriptFunction func, ScriptAny[] args, ScriptAny thisObj = ScriptAny.UNDEFINED) 35 { 36 // first get the thisObj 37 bool _; // @suppress(dscanner.suspicious.unmodified) 38 if(thisObj == ScriptAny.UNDEFINED) 39 { 40 if(func.boundThis) 41 { 42 thisObj = func.boundThis; 43 } 44 if(func.closure && func.closure.variableOrConstExists("this")) 45 { 46 thisObj = *func.closure.lookupVariableOrConst("this", _); 47 } 48 else if(env.variableOrConstExists("this")) 49 { 50 thisObj = *env.lookupVariableOrConst("this", _); 51 } 52 // else it's undefined and that's ok 53 } 54 55 // next get a VM copy that will live in the following closure 56 if(env.getGlobalEnvironment.interpreter.vm is null) 57 throw new Exception("Generators may only be used in VM mode"); 58 auto vm = env.getGlobalEnvironment.interpreter.vm.copy(); 59 60 _name = func.functionName; 61 62 super({ 63 _returnValue = vm.runFunction(func, thisObj, args, ScriptAny(new ScriptFunction("yield", 64 &this.native_yield))); 65 }); 66 } 67 68 override string toString() const 69 { 70 return "Generator " ~ _name; 71 } 72 private: 73 74 ScriptAny native_yield(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 75 { 76 if(args.length < 1) 77 .yield!ScriptAny(ScriptAny()); 78 else 79 .yield!ScriptAny(args[0]); 80 auto result = this._yieldValue; 81 this._yieldValue = ScriptAny.UNDEFINED; 82 return result; 83 } 84 85 string _name; 86 bool _markedAsFinished = false; 87 ScriptAny _yieldValue; 88 ScriptAny _returnValue; 89 } 90 91 /// initialize the Generator library 92 void initializeGeneratorLibrary(Interpreter interpreter) 93 { 94 ScriptAny ctor = new ScriptFunction("Generator", &native_Generator_ctor, true); 95 ctor["prototype"] = getGeneratorPrototype(); 96 ctor["prototype"]["constructor"] = ctor; 97 interpreter.forceSetGlobal("Generator", ctor, false); 98 } 99 100 private ScriptObject _generatorPrototype; 101 102 /// Gets the Generator prototype. The VM will eventually need this. 103 ScriptObject getGeneratorPrototype() 104 { 105 if(_generatorPrototype is null) 106 { 107 _generatorPrototype = new ScriptObject("Generator", null); 108 _generatorPrototype.addGetterProperty("name", new ScriptFunction("Generator.prototype.name", 109 &native_Generator_p_name)); 110 _generatorPrototype["next"] = new ScriptFunction("Generator.prototype.next", 111 &native_Generator_next); 112 _generatorPrototype["return"] = new ScriptFunction("Generator.prototype.return", 113 &native_Generator_return); 114 _generatorPrototype.addGetterProperty("returnValue", new ScriptFunction("Generator.prototype.returnValue", 115 &native_Generator_p_returnValue)); 116 } 117 return _generatorPrototype; 118 } 119 120 private ScriptAny native_Generator_ctor(Environment env, ScriptAny* thisObj, 121 ScriptAny[] args, ref NativeFunctionError nfe) 122 { 123 if(args.length < 1 || args[0].type != ScriptAny.Type.FUNCTION) 124 { 125 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 126 return ScriptAny("First argument to new Generator() must exist and be a Function"); 127 } 128 if(!thisObj.isObject) 129 { 130 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 131 return ScriptAny.UNDEFINED; 132 } 133 auto obj = thisObj.toValue!ScriptObject; 134 obj.nativeObject = new ScriptGenerator(env, args[0].toValue!ScriptFunction, args[1..$]); 135 return ScriptAny.UNDEFINED; 136 } 137 138 private ScriptAny native_Generator_p_name(Environment env, ScriptAny* thisObj, 139 ScriptAny[] args, ref NativeFunctionError nfe) 140 { 141 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 142 if(thisGen is null) 143 { 144 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 145 return ScriptAny.UNDEFINED; 146 } 147 return ScriptAny(thisGen._name); 148 } 149 150 /// This is public for opIter 151 ScriptAny native_Generator_next(Environment env, ScriptAny* thisObj, 152 ScriptAny[] args, ref NativeFunctionError nfe) 153 { 154 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 155 if(thisGen is null) 156 { 157 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 158 return ScriptAny.UNDEFINED; 159 } 160 auto valueToYield = args.length > 0 ? args[0] : ScriptAny.UNDEFINED; // @suppress(dscanner.suspicious.unmodified) 161 thisGen._yieldValue = valueToYield; 162 163 if(thisGen._markedAsFinished) 164 { 165 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 166 return ScriptAny("Cannot call next on a finished Generator"); 167 } 168 if(!thisGen.empty) 169 { 170 auto obj = new ScriptObject("iteration", null); 171 obj["done"] = ScriptAny(false); 172 obj["value"] = thisGen.front(); 173 try 174 { 175 thisGen.popFront(); 176 } 177 catch(ScriptRuntimeException ex) 178 { 179 thisGen._markedAsFinished = true; 180 return ScriptAny.UNDEFINED; 181 } 182 return ScriptAny(obj); 183 } 184 else 185 { 186 thisGen._markedAsFinished = true; 187 auto obj = new ScriptObject("iteration", null); 188 obj["done"] = ScriptAny(true); 189 return ScriptAny(obj); 190 } 191 } 192 193 private ScriptAny native_Generator_return(Environment env, ScriptAny* thisObj, 194 ScriptAny[] args, ref NativeFunctionError nfe) 195 { 196 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 197 if(thisGen is null) 198 { 199 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 200 return ScriptAny.UNDEFINED; 201 } 202 ScriptAny retVal = args.length > 0 ? args[0] : ScriptAny.UNDEFINED; // @suppress(dscanner.suspicious.unmodified) 203 if(thisGen._markedAsFinished) 204 { 205 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 206 return ScriptAny("Cannot call return on a finished Generator"); 207 } 208 while(!thisGen.empty) 209 { 210 try 211 { 212 thisGen.popFront(); 213 } 214 catch(ScriptRuntimeException ex) 215 { 216 thisGen._markedAsFinished = true; 217 return ScriptAny.UNDEFINED; 218 } 219 } 220 thisGen._markedAsFinished = true; 221 auto obj = new ScriptObject("iteration", null); 222 obj["value"] = retVal; 223 obj["done"] = ScriptAny(true); 224 return ScriptAny(obj); 225 } 226 227 // we are not adding throw I've ripped up the Lexer enough as it is 228 229 private ScriptAny native_Generator_p_returnValue(Environment env, ScriptAny* thisObj, 230 ScriptAny[] args, ref NativeFunctionError nfe) 231 { 232 auto thisGen = thisObj.toNativeObject!ScriptGenerator; 233 if(thisGen is null) 234 { 235 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 236 return ScriptAny.UNDEFINED; 237 } 238 return thisGen._returnValue; 239 } 240