1 /** 2 * Tcl interpreter module. 3 * 4 * License: 5 * MIT. See LICENSE for full details. 6 */ 7 module tkd.interpreter.tcl; 8 9 /** 10 * Imports. 11 */ 12 import std.conv; 13 import std.regex : Captures, regex, replaceAll; 14 import std.stdio; 15 import std.string; 16 import tcltk.tcl; 17 import tkd.interpreter.logger; 18 19 /** 20 * Simple wrapper for the Tcl interpreter. 21 */ 22 class Tcl 23 { 24 /* 25 * An instance of the native tcl interpreter. 26 */ 27 protected Tcl_Interp* _interpreter; 28 29 /** 30 * An instance of this tcl interpreter. 31 */ 32 private static Tcl _instance; 33 34 /* 35 * The logger. 36 */ 37 protected Logger _log; 38 39 /* 40 * Create the interpreter and initialise it. 41 * 42 * Throws: 43 * Exception if Tcl interpreter cannot be initialised. 44 */ 45 protected this() 46 { 47 debug (log) this._log = new Logger("debug.log"); 48 debug (log) this._log.info("Inititalising Tcl"); 49 50 this._interpreter = Tcl_CreateInterp(); 51 52 if (Tcl_Init(this._interpreter) != TCL_OK) 53 { 54 string result = Tcl_GetStringResult(this._interpreter).to!(string); 55 throw new Exception(format("Tcl interpreter could not be initialised. %s", result)); 56 } 57 } 58 59 /* 60 * Clean up. 61 */ 62 protected ~this() 63 { 64 Tcl_DeleteInterp(this._interpreter); 65 } 66 67 /** 68 * Get an instance of this class. 69 * 70 * Returns: 71 * If An instance doesn't exist, one is created and returned. 72 * If one already exists, that is returned. 73 */ 74 public static Tcl getInstance() 75 { 76 if (Tcl._instance is null) 77 { 78 Tcl._instance = new Tcl(); 79 } 80 return Tcl._instance; 81 } 82 83 /** 84 * Escape harmful characters in arguments that are to be used in a script. 85 * 86 * Params: 87 * args = An array of arguments to escape. 88 * 89 * Returns: 90 * Escaped arguments. 91 */ 92 public string[] escape(string[] args) 93 { 94 foreach (ref arg; args) 95 { 96 arg = this.escape(arg); 97 } 98 return args; 99 } 100 101 /** 102 * Escape harmful characters in the passed argument that is to be used in a 103 * script. 104 * 105 * Params: 106 * arg = The argument to escape. 107 * 108 * Returns: 109 * The argument. 110 */ 111 public string escape(string arg) 112 { 113 string replacer(Captures!(string) m) 114 { 115 final switch(m.hit) 116 { 117 case `\`: 118 return `\\`; 119 120 case `"`: 121 return `\"`; 122 123 case `$`: 124 return `\$`; 125 126 case `[`: 127 return `\[`; 128 129 case `]`: 130 return `\]`; 131 } 132 assert(false); 133 } 134 return arg.replaceAll!(replacer)(regex(`\\|"|\$|\[|\]`)); 135 } 136 137 /** 138 * Evaluate a script fragment using the interpreter. 139 * 140 * Params: 141 * script = The script to evaluate, including any format placeholders. 142 * args = variadic list of arguments to provide data for any format placeholders. 143 */ 144 public void eval(A...)(string script, A args) 145 { 146 foreach (ref arg; args) 147 { 148 static if (is(typeof(arg) == string)) 149 { 150 arg = this.escape(arg); 151 } 152 } 153 154 debug (log) this._log.eval(script, args); 155 156 static if (A.length) 157 { 158 script = format(script, args); 159 } 160 161 int result = Tcl_EvalEx(this._interpreter, script.toStringz, -1, 0); 162 163 if (result == TCL_ERROR) 164 { 165 string error = Tcl_GetStringResult(this._interpreter).to!(string); 166 167 debug (showTclErrors) 168 { 169 writeln(error); 170 } 171 172 debug (log) this._log.warning(error); 173 } 174 } 175 176 /** 177 * Set the result of the interpreter. 178 * This is sometimes used to set the result to an error if things go bad. 179 * 180 * Params: 181 * result = The text to set as the result, including any format placeholders. 182 * args = variadic list of arguments to provide data for any format placeholders. 183 */ 184 public void setResult(A...)(string result, A args) 185 { 186 static if (A.length) 187 { 188 result = format(result, args); 189 } 190 191 debug (log) this._log.info("Setting interpreter result '%s'", result); 192 Tcl_SetResult(this._interpreter, result.toStringz, TCL_STATIC); 193 } 194 195 /** 196 * Get the result string from the interpreter. 197 * 198 * Returns: 199 * A string representing the result of the last script fragment evaluated. 200 */ 201 public T getResult(T)() 202 { 203 string result = Tcl_GetStringResult(this._interpreter).to!(string); 204 debug (log) this._log.info("Getting interpreter result '%s'", result); 205 return result.to!(T); 206 } 207 208 /** 209 * Create a new command in the Tcl interpreter. 210 * 211 * Params: 212 * name = The name of the new command. 213 * commandProcedure = A function pointer to the new command. 214 * data = Extra data to be passed to the command on invocation. 215 * deleteProcedure = The procedure to run when deleteCommand is called. 216 * 217 * Returns: 218 * A command token that can be used to refer to the command created. 219 */ 220 public Tcl_Command createCommand(string name, Tcl_CmdProc commandProcedure, ClientData data = null, Tcl_CmdDeleteProc deleteProcedure = null) 221 { 222 debug (log) this._log.info("Creating command %s", name); 223 return Tcl_CreateCommand(this._interpreter, name.toStringz, commandProcedure, data, deleteProcedure); 224 } 225 226 /** 227 * Delete a command in the Tcl interpreter. 228 * 229 * Params: 230 * name = The name of the command to delete. 231 */ 232 public void deleteCommand(string name) 233 { 234 debug (log) this._log.info("Deleting command %s", name); 235 236 int result = Tcl_DeleteCommand(this._interpreter, name.toStringz); 237 238 if (result == TCL_ERROR) 239 { 240 string error = Tcl_GetStringResult(this._interpreter).to!(string); 241 242 debug (showTclErrors) 243 { 244 writeln(error); 245 } 246 247 debug (log) this._log.warning(error); 248 } 249 } 250 251 /** 252 * Set the value of a variable. 253 * If the variable doesn't exist it is created. 254 * 255 * Params: 256 * name = The name of the variable to set. 257 * value = The variable's value. 258 */ 259 public void setVariable(T)(string name, T value) 260 { 261 debug (log) this._log.info("Setting variable %s <- '%s'", name, value); 262 Tcl_SetVar(this._interpreter, name.toStringz, value.to!(string).toStringz, TCL_GLOBAL_ONLY); 263 } 264 265 /** 266 * Get the value of a variable. 267 * 268 * Params: 269 * name = The name of the variable to get the value of. 270 * 271 * Returns: 272 * A string containing the variable's value. 273 */ 274 public string getVariable(string name) 275 { 276 string result = Tcl_GetVar(this._interpreter, name.toStringz, TCL_GLOBAL_ONLY).to!(string); 277 debug (log) this._log.info("Getting variable %s -> '%s'", name, result); 278 return result; 279 } 280 281 /** 282 * Delete a variable from the interpreter. 283 * 284 * Params: 285 * name = The name of the variable to delete. 286 */ 287 public void deleteVariable(string name) 288 { 289 debug (log) this._log.info("Deleting variable %s", name); 290 291 int result = Tcl_UnsetVar(this._interpreter, name.toStringz, TCL_GLOBAL_ONLY); 292 293 if (result == TCL_ERROR) 294 { 295 string error = Tcl_GetStringResult(this._interpreter).to!(string); 296 297 debug (showTclErrors) 298 { 299 writeln(error); 300 } 301 302 debug (log) this._log.warning(error); 303 } 304 } 305 306 }