1 /**
2 This module implements the Mildew Map class, which allows all Mildew types to serve as a key in a hash map.
3 See https://pillager86.github.io/dmildew/Map.html for usage.
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.map;
22 
23 import mildew.environment;
24 import mildew.interpreter;
25 import mildew.types;
26 
27 /// D class wrapper around a hash map that can be stored in ScriptObject's nativeObject field
28 class ScriptMap
29 {
30     /// the entries
31     ScriptAny[ScriptAny] entries;
32 
33     override string toString() const 
34     {
35         import std.conv: to;
36         return "Map (" ~ entries.length.to!string ~ ")";
37     }
38 }
39 
40 /** 
41  * Initializes the Map class in the scripts. Documentation for usage can be found at
42  * https://pillager86.github.io/dmildew/Map.html
43  * Params:
44  *  interpreter = The Interpreter instance to load the Map constructor as a global into.
45  */
46 void initializeMapLibrary(Interpreter interpreter)
47 {
48     ScriptAny ctor = new ScriptFunction("Map", &native_Map_ctor, true);
49     ctor["prototype"] = getMapPrototype();
50     ctor["prototype"]["constructor"] = ctor;
51     interpreter.forceSetGlobal("Map", ctor);
52 }
53 
54 private ScriptObject _mapPrototype;
55 
56 private ScriptObject getMapPrototype()
57 {
58     if(_mapPrototype is null)
59     {
60         _mapPrototype = new ScriptObject("Map", null);
61         _mapPrototype["clear"] = new ScriptFunction("Map.prototype.clear", &native_Map_clear);
62         _mapPrototype["delete"] = new ScriptFunction("Map.prototype.delete", &native_Map_delete);
63         _mapPrototype["entries"] = new ScriptFunction("Map.prototype.entries", &native_Map_entries);
64         _mapPrototype["forEach"] = new ScriptFunction("Map.prototype.forEach", &native_Map_forEach);
65         _mapPrototype["get"] = new ScriptFunction("Map.prototype.get", &native_Map_get);
66         _mapPrototype["has"] = new ScriptFunction("Map.prototype.has", &native_Map_has);
67         _mapPrototype["keys"] = new ScriptFunction("Map.prototype.keys", &native_Map_keys);
68         _mapPrototype.addGetterProperty("length", new ScriptFunction("Map.prototype.length", &native_Map_p_length));
69         _mapPrototype["set"] = new ScriptFunction("Map.prototype.set", &native_Map_set);
70         _mapPrototype["values"] = new ScriptFunction("Map.prototype.values", &native_Map_values);
71 
72     }
73     return _mapPrototype;
74 }
75 
76 private ScriptAny native_Map_ctor(Environment env, ScriptAny* thisObj,
77                                   ScriptAny[] args, ref NativeFunctionError nfe)
78 {
79     if(!thisObj.isObject)
80     {
81         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
82         return ScriptAny.UNDEFINED;
83     }
84     thisObj.toValue!ScriptObject().nativeObject = new ScriptMap();
85     return ScriptAny.UNDEFINED; // could return this for Map() usage.
86 }
87 
88 private ScriptAny native_Map_clear(Environment env, ScriptAny* thisObj,
89                                    ScriptAny[] args, ref NativeFunctionError nfe)
90 {
91     auto map = thisObj.toNativeObject!ScriptMap;
92     if(map is null)
93     {
94         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
95         return ScriptAny.UNDEFINED;
96     }
97     map.entries.clear();
98     return ScriptAny.UNDEFINED;
99 }
100 
101 private ScriptAny native_Map_delete(Environment env, ScriptAny* thisObj,
102                                     ScriptAny[] args, ref NativeFunctionError nfe)
103 {
104     auto map = thisObj.toNativeObject!ScriptMap;
105     if(map is null)
106     {
107         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
108         return ScriptAny.UNDEFINED;
109     }
110     if(args.length < 1)
111     {
112         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
113         return ScriptAny.UNDEFINED;
114     }
115     immutable bool removed = (args[0] in map.entries) != null;
116     map.entries.remove(args[0]);
117     return ScriptAny(removed);
118 }
119 
120 private ScriptAny native_Map_entries(Environment env, ScriptAny* thisObj,
121                                      ScriptAny[] args, ref NativeFunctionError nfe)
122 {
123     import mildew.stdlib.generator: ScriptGenerator, getGeneratorPrototype;
124 
125     auto map = thisObj.toNativeObject!ScriptMap; // @suppress(dscanner.suspicious.unmodified)
126     if(map is null)
127     {
128         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
129         return ScriptAny.UNDEFINED;
130     }
131     auto func = new ScriptFunction("Iterator", 
132         cast(NativeFunction)(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe){
133             import std.concurrency: yield;
134             auto map = args[0].toNativeObject!ScriptMap;
135                 foreach(key, value ; map.entries)
136                 {
137                     ScriptAny[] entry = [key, value];
138                     yield!ScriptAny(ScriptAny(entry));
139                 }
140                 return ScriptAny.UNDEFINED;
141         });
142     auto generator = new ScriptGenerator(env, func, [ *thisObj ] );
143     auto obj = new ScriptObject("Iterator", getGeneratorPrototype, generator);
144     return ScriptAny(obj);
145 }
146 
147 private ScriptAny native_Map_forEach(Environment env, ScriptAny* thisObj,
148                                      ScriptAny[] args, ref NativeFunctionError nfe)
149 {
150     import mildew.types.bindings: native_Function_call, getLocalThis;
151 
152     auto map = thisObj.toNativeObject!ScriptMap;
153     if(args.length < 1)
154     {
155         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
156         return ScriptAny.UNDEFINED;
157     }
158     if(map is null || args[0].type != ScriptAny.Type.FUNCTION)
159     {
160         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
161         return ScriptAny.UNDEFINED;
162     }
163     ScriptAny thisToUse;
164     if(args.length > 1)
165         thisToUse = args[1];
166     else
167         thisToUse = getLocalThis(env, args[0]);
168     foreach(key, value ; map.entries)
169     {
170         auto temp = native_Function_call(env, &args[0], [thisToUse, value, key, *thisObj], nfe);
171         if(env.g.interpreter.vm.hasException)
172             return temp;
173         if(nfe != NativeFunctionError.NO_ERROR)
174             return temp;
175     }
176     return ScriptAny.UNDEFINED;
177 }
178 
179 private ScriptAny native_Map_get(Environment env, ScriptAny* thisObj,
180                                  ScriptAny[] args, ref NativeFunctionError nfe)
181 {
182     auto map = thisObj.toNativeObject!ScriptMap; // @suppress(dscanner.suspicious.unmodified)
183     if(map is null)
184     {
185         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
186         return ScriptAny.UNDEFINED;
187     }
188     if(args.length < 1)
189     {
190         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
191         return ScriptAny.UNDEFINED;
192     }
193     auto resultPtr = args[0] in map.entries;
194     if(resultPtr == null)
195         return ScriptAny.UNDEFINED;
196     return *resultPtr;
197 }
198 
199 private ScriptAny native_Map_has(Environment env, ScriptAny* thisObj,
200                                  ScriptAny[] args, ref NativeFunctionError nfe)
201 {
202     auto map = thisObj.toNativeObject!ScriptMap; // @suppress(dscanner.suspicious.unmodified)
203     if(map is null)
204     {
205         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
206         return ScriptAny.UNDEFINED;
207     }
208     if(args.length < 1)
209     {
210         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
211         return ScriptAny.UNDEFINED;
212     }
213     auto resultPtr = args[0] in map.entries; // @suppress(dscanner.suspicious.unmodified)
214     if(resultPtr == null)
215         return ScriptAny(false);
216     return ScriptAny(true);
217 }
218 
219 private ScriptAny native_Map_keys(Environment env, ScriptAny* thisObj,
220                                      ScriptAny[] args, ref NativeFunctionError nfe)
221 {
222     import mildew.stdlib.generator: ScriptGenerator, getGeneratorPrototype;
223 
224     auto map = thisObj.toNativeObject!ScriptMap; // @suppress(dscanner.suspicious.unmodified)
225     if(map is null)
226     {
227         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
228         return ScriptAny.UNDEFINED;
229     }
230     auto func = new ScriptFunction("Iterator", 
231         cast(NativeFunction)(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe){
232             import std.concurrency: yield;
233             auto map = args[0].toNativeObject!ScriptMap;
234                 foreach(key ; map.entries.keys)
235                 {
236                     yield!ScriptAny(key);
237                 }
238                 return ScriptAny.UNDEFINED;
239         });
240     auto generator = new ScriptGenerator(env, func, [ *thisObj ] );
241     auto obj = new ScriptObject("Iterator", getGeneratorPrototype, generator);
242     return ScriptAny(obj);
243 }
244 
245 private ScriptAny native_Map_p_length(Environment env, ScriptAny* thisObj,
246                                       ScriptAny[] args, ref NativeFunctionError nfe)
247 {
248     auto map = thisObj.toNativeObject!ScriptMap;
249     if(map is null)
250     {
251         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
252         return ScriptAny.UNDEFINED;
253     }
254     return ScriptAny(map.entries.length);
255 }
256 
257 private ScriptAny native_Map_set(Environment env, ScriptAny* thisObj,
258                                  ScriptAny[] args, ref NativeFunctionError nfe)
259 {
260     auto map = thisObj.toNativeObject!ScriptMap; // @suppress(dscanner.suspicious.unmodified)
261     if(map is null)
262     {
263         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
264         return ScriptAny.UNDEFINED;
265     }
266     if(args.length < 2)
267     {
268         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
269         return ScriptAny.UNDEFINED;
270     }
271     map.entries[args[0]] = args[1];
272     return *thisObj;
273 }
274 
275 private ScriptAny native_Map_values(Environment env, ScriptAny* thisObj,
276                                      ScriptAny[] args, ref NativeFunctionError nfe)
277 {
278     import mildew.stdlib.generator: ScriptGenerator, getGeneratorPrototype;
279 
280     auto map = thisObj.toNativeObject!ScriptMap; // @suppress(dscanner.suspicious.unmodified)
281     if(map is null)
282     {
283         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
284         return ScriptAny.UNDEFINED;
285     }
286     auto func = new ScriptFunction("Iterator", 
287         cast(NativeFunction)(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe){
288             import std.concurrency: yield;
289             auto map = args[0].toNativeObject!ScriptMap;
290                 foreach(value ; map.entries.values)
291                 {
292                     yield!ScriptAny(value);
293                 }
294                 return ScriptAny.UNDEFINED;
295         });
296     auto generator = new ScriptGenerator(env, func, [ *thisObj ] );
297     auto obj = new ScriptObject("Iterator", getGeneratorPrototype, generator);
298     return ScriptAny(obj);
299 }