1 /** 2 This module implements the Environment class. 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.environment; 21 22 import std.container.rbtree; 23 24 import mildew.types.any; 25 import mildew.interpreter: Interpreter; 26 27 private alias VariableTable = ScriptAny[string]; 28 29 /** 30 * Holds the variables and consts of a script stack frame. The global environment can be accessed by 31 * climbing the Environment.parent chain until reaching the Environment whose parent is null. This allows 32 * native functions to define local and global variables. Note that calling a native function does 33 * not create a stack frame so one could write a native function that adds local variables to the 34 * stack frame where it was called. NOTE: It is not possible to access integral for loop variables 35 * from an Environment when using VM mode. 36 */ 37 class Environment 38 { 39 public: 40 /** 41 * Constructs a new Environment. 42 * Params: 43 * par = The parent environment, which should be null when the global environment is created 44 * nam = The name of the environment. When script functions are called this is set to the name 45 * of the function being called. 46 */ 47 this(Environment par = null, in string nam = "<environment>") 48 { 49 _parent = par; 50 _name = nam; 51 } 52 53 /** 54 * Constructs a global environment 55 */ 56 this(Interpreter interpreter) 57 { 58 _parent = null; 59 _name = "<global>"; 60 _interpreter = interpreter; 61 } 62 63 /** 64 * Attempts to look up existing variable or const throughout the stack. If found, returns a pointer to the 65 * variable location, and if it is const, sets isConst to true. Note, this pointer should not be stored by 66 * native functions because the variable table may be modified between function calls. 67 * Params: 68 * name = The name of the variable to look up. 69 * isConst = Whether or not the found variable is constant. Will remain false if variable is not found 70 * Returns: 71 * A pointer to the located variable, or null if the variable was not found. If this value is needed for later 72 * the caller should make a copy of the variable immediately. 73 */ 74 ScriptAny* lookupVariableOrConst(in string varName, out bool isConst) 75 { 76 auto environment = this; 77 while(environment !is null) 78 { 79 if(varName in environment._constTable) 80 { 81 isConst = true; 82 return (varName in environment._constTable); 83 } 84 if(varName in environment._varTable) 85 { 86 isConst = false; 87 return (varName in environment._varTable); 88 } 89 environment = environment._parent; 90 } 91 isConst = false; 92 return null; // found nothing 93 } 94 95 /** 96 * Removes a variable from anywhere on the Environment stack it is located. This function cannot 97 * be used to unset consts. 98 * Params: 99 * name = The name of the variable. 100 */ 101 void unsetVariable(in string name) 102 { 103 auto environment = this; 104 while(environment !is null) 105 { 106 if(name in environment._varTable) 107 { 108 environment._varTable.remove(name); 109 return; 110 } 111 environment = environment._parent; 112 } 113 } 114 115 /** 116 * Attempt to declare and assign a new variable in the current environment. Returns false if it already exists. 117 * Params: 118 * nam = the name of the variable to set. 119 * value = the initial value of the variable. This can be ScriptAny.UNDEFINED 120 * isConst = whether or not the variable was declared as a const 121 * Returns: 122 * True if the declaration was successful, otherwise false. 123 */ 124 bool declareVariableOrConst(in string nam, ScriptAny value, in bool isConst) 125 { 126 if(nam in _varTable || nam in _constTable) 127 return false; 128 129 if(isConst) 130 { 131 _constTable[nam] = value; 132 } 133 else 134 { 135 _varTable[nam] = value; 136 } 137 return true; 138 } 139 140 /** 141 * Searches the entire Environment stack for a variable starting with the current environment and climbing the parent 142 * chain. 143 * Params: 144 * name = The name of the variable to look for. 145 * Returns: 146 * True if the variable is found, otherwise false. 147 */ 148 bool variableOrConstExists(in string name) 149 { 150 auto environment = this; 151 while(environment !is null) 152 { 153 if(name in environment._constTable) 154 return true; 155 if(name in environment._varTable) 156 return true; 157 environment = environment._parent; 158 } 159 return false; 160 } 161 162 /** 163 * Attempts to reassign a variable anywhere in the stack and returns a pointer to the variable or null 164 * if the variable doesn't exist or is const. If the failure is due to const, failedBecauseConst is 165 * set to true. Note: this pointer should not be stored by native functions due to modifications 166 * to the variable table that may invalidate it and result in undefined behavior. 167 * Params: 168 * name = The name of the variable to reassign. 169 * newValue = The value to assign. If this is undefined and the variable isn't const, the variable 170 * will be deleted from the table where it is found. 171 * failedBecauseConst = If the reassignment fails due to the variable being a const, this is set to true 172 * Returns: 173 * A pointer to the variable in the table where it is found, or null if it was const or not located. 174 */ 175 ScriptAny* reassignVariable(in string name, ScriptAny newValue, out bool failedBecauseConst) 176 { 177 bool isConst; // @suppress(dscanner.suspicious.unmodified) 178 auto scriptAnyPtr = lookupVariableOrConst(name, isConst); 179 if(scriptAnyPtr == null) 180 { 181 failedBecauseConst = false; 182 return null; 183 } 184 if(isConst) 185 { 186 failedBecauseConst = true; 187 return null; 188 } 189 *scriptAnyPtr = newValue; 190 failedBecauseConst = false; 191 return scriptAnyPtr; 192 } 193 194 /** 195 * Force sets a variable or const no matter if the variable was declared already or is const. This is 196 * used by the host application to set globals or locals. 197 * Params: 198 * name = The name of the variable or const 199 * value = The value of the variable 200 * isConst = Whether or not the variable should be considered const and unable to be overwritten by the script 201 */ 202 void forceSetVarOrConst(in string name, ScriptAny value, bool isConst) 203 { 204 if(isConst) 205 { 206 _constTable[name] = value; 207 } 208 else 209 { 210 _varTable[name] = value; 211 } 212 } 213 214 /** 215 * Forces the removal of a const or variable in the current environment. 216 */ 217 void forceRemoveVarOrConst(in string name) 218 { 219 if(name in _constTable) 220 _constTable.remove(name); 221 if(name in _varTable) 222 _varTable.remove(name); 223 } 224 225 /// climb environment stack until finding one without a parent 226 Environment getGlobalEnvironment() 227 { 228 Environment c = this; 229 while(c._parent !is null) 230 { 231 c = c._parent; 232 } 233 return c; 234 } 235 236 /// inserts a label into the list of valid labels 237 deprecated void insertLabel(string label) 238 { 239 _labelList.insert(label); 240 } 241 242 /// Retrieves the interpreter object from the top level environment 243 Interpreter interpreter() 244 { 245 auto search = this; 246 while(search !is null) 247 { 248 if(search._interpreter !is null) 249 return search._interpreter; 250 search = search._parent; 251 } 252 return null; 253 } 254 255 /// checks environment stack for a label 256 deprecated bool labelExists(string label) 257 { 258 auto environment = this; 259 while(environment !is null) 260 { 261 if(label in environment._labelList) 262 return true; 263 environment = environment._parent; 264 } 265 return false; 266 } 267 268 /// removes a label from the existing environment 269 deprecated void removeLabelFromCurrent(string label) 270 { 271 _labelList.removeKey(label); 272 } 273 274 /// returns the parent property 275 Environment parent() 276 { 277 return _parent; 278 } 279 280 /// Returns the depth from this Environment to the root Environment 281 size_t depth() 282 { 283 size_t d = 0; 284 auto env = _parent; 285 while(env !is null) 286 { 287 ++d; 288 env = env._parent; 289 } 290 return d; 291 } 292 293 /// returns the name property of the Environment 294 string name() const 295 { 296 return _name; 297 } 298 299 /// Returns a string representing the type and name 300 override string toString() const 301 { 302 return "Environment: " ~ _name; 303 } 304 305 /// Returns the level of depth, relative to this Environment, 0-N for a variable location, or -1 if not found 306 int varDepth(string varName, out bool isConst) 307 { 308 int depth = 0; 309 auto env = this; 310 isConst = false; 311 while(env !is null) 312 { 313 if(varName in env._constTable) 314 { 315 isConst = true; 316 return depth; 317 } 318 if(varName in env._varTable) 319 return depth; 320 env = env._parent; 321 ++depth; 322 } 323 return -1; 324 } 325 326 private: 327 328 /// parent environment. null if this is the global environment 329 Environment _parent; 330 /// name of environment 331 string _name; 332 /// holds variables 333 VariableTable _varTable; 334 /// holds consts, which can be shadowed by other consts or lets 335 VariableTable _constTable; 336 /// holds a list of labels 337 auto _labelList = new RedBlackTree!string; 338 /// Interpreter object can be held by global environment 339 Interpreter _interpreter; 340 }