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 }