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