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