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 : 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 the script before evaluation. 85 * 86 * Params: 87 * arg = The argument to escape. 88 * 89 * Returns: 90 * The escaped script. 91 */ 92 private string escapeArg(string arg) 93 { 94 // Allow backslashes to be passed as intended to Tcl. 95 arg = arg.replaceAll(regex(r"\\"), r"\\"); 96 97 // Braces are used as string delimeters in Tcl so escape those. 98 arg = arg.replaceAll(regex(r"\}"), r"\}"); 99 arg = arg.replaceAll(regex(r"\{"), r"\{"); 100 101 return arg; 102 } 103 104 /** 105 * Evaluate a script fragment using the interpreter. 106 * 107 * Params: 108 * script = The script to evaluate, including any format placeholders. 109 * args = variadic list of arguments to provide data for any format placeholders. 110 */ 111 public void eval(A...)(string script, A args) 112 { 113 foreach (ref arg; args) 114 { 115 static if (is(typeof(arg) == string)) 116 { 117 arg = this.escapeArg(arg); 118 } 119 } 120 121 debug (log) this._log.eval(script, args); 122 123 int result = Tcl_EvalEx(this._interpreter, format(script, args).toStringz, -1, 0); 124 125 if (result == TCL_ERROR) 126 { 127 string error = Tcl_GetStringResult(this._interpreter).to!(string); 128 129 debug (showTclErrors) 130 { 131 writeln(error); 132 } 133 134 debug (log) this._log.warning(error); 135 } 136 } 137 138 /** 139 * Set the result of the interpreter. 140 * This is sometimes used to set the result to an error if things go bad. 141 * 142 * Params: 143 * result = The text to set as the result, including any format placeholders. 144 * args = variadic list of arguments to provide data for any format placeholders. 145 */ 146 public void setResult(A...)(string result, A args) 147 { 148 debug (log) this._log.info("Setting interpreter result '%s'", format(result, args)); 149 Tcl_SetResult(this._interpreter, format(result, args).toStringz, TCL_STATIC); 150 } 151 152 /** 153 * Get the result string from the interpreter. 154 * 155 * Returns: 156 * A string representing the result of the last script fragment evaluated. 157 */ 158 public T getResult(T)() 159 { 160 string result = Tcl_GetStringResult(this._interpreter).to!(string); 161 debug (log) this._log.info("Getting interpreter result '%s'", result); 162 return result.to!(T); 163 } 164 165 /** 166 * Create a new command in the Tcl interpreter. 167 * 168 * Params: 169 * name = The name of the new command. 170 * commandProcedure = A function pointer to the new command. 171 * data = Extra data to be passed to the command on invocation. 172 * deleteProcedure = The procedure to run when deleteCommand is called. 173 * 174 * Returns: 175 * A command token that can be used to refer to the command created. 176 */ 177 public Tcl_Command createCommand(string name, Tcl_CmdProc commandProcedure, ClientData data = null, Tcl_CmdDeleteProc deleteProcedure = null) 178 { 179 debug (log) this._log.info("Creating command %s", name); 180 return Tcl_CreateCommand(this._interpreter, name.toStringz, commandProcedure, data, deleteProcedure); 181 } 182 183 /** 184 * Delete a command in the Tcl interpreter. 185 * 186 * Params: 187 * name = The name of the command to delete. 188 */ 189 public void deleteCommand(string name) 190 { 191 debug (log) this._log.info("Deleting command %s", name); 192 193 int result = Tcl_DeleteCommand(this._interpreter, name.toStringz); 194 195 if (result == TCL_ERROR) 196 { 197 string error = Tcl_GetStringResult(this._interpreter).to!(string); 198 199 debug (showTclErrors) 200 { 201 writeln(error); 202 } 203 204 debug (log) this._log.warning(error); 205 } 206 } 207 208 /** 209 * Set the value of a variable. 210 * If the variable doesn't exist it is created. 211 * 212 * Params: 213 * name = The name of the variable to set. 214 * value = The variable's value. 215 */ 216 public void setVariable(T)(string name, T value) 217 { 218 debug (log) this._log.info("Setting variable %s <- '%s'", name, value); 219 Tcl_SetVar(this._interpreter, name.toStringz, value.to!(string).toStringz, TCL_GLOBAL_ONLY); 220 } 221 222 /** 223 * Get the value of a variable. 224 * 225 * Params: 226 * name = The name of the variable to get the value of. 227 * 228 * Returns: 229 * A string containing the variable's value. 230 */ 231 public string getVariable(string name) 232 { 233 string result = Tcl_GetVar(this._interpreter, name.toStringz, TCL_GLOBAL_ONLY).to!(string); 234 debug (log) this._log.info("Getting variable %s -> '%s'", name, result); 235 return result; 236 } 237 238 /** 239 * Delete a variable from the interpreter. 240 * 241 * Params: 242 * name = The name of the variable to delete. 243 */ 244 public void deleteVariable(string name) 245 { 246 debug (log) this._log.info("Deleting variable %s", name); 247 248 int result = Tcl_UnsetVar(this._interpreter, name.toStringz, TCL_GLOBAL_ONLY); 249 250 if (result == TCL_ERROR) 251 { 252 string error = Tcl_GetStringResult(this._interpreter).to!(string); 253 254 debug (showTclErrors) 255 { 256 writeln(error); 257 } 258 259 debug (log) this._log.warning(error); 260 } 261 } 262 263 }