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