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 }