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