35static std::vector<Trace> traces;
36static uintptr_t traceCount = 0;
39static int dummyClose(ClientData , Tcl_Interp* )
43static int dummyInput(ClientData ,
char* ,
48static void dummyWatch(ClientData ,
int )
51static int dummyGetHandle(ClientData ,
int ,
56Tcl_ChannelType Interpreter::channelType = {
57 const_cast<char*
>(
"openMSX console"),
61 Interpreter::outputProc,
78 Tcl_FindExecutable(programName);
82 : interp(Tcl_CreateInterp())
99 Tcl_Channel channel = Tcl_CreateChannel(&channelType,
100 "openMSX console",
this, TCL_WRITABLE);
102 Tcl_SetChannelOption(interp, channel,
"-translation",
"binary");
103 Tcl_SetChannelOption(interp, channel,
"-buffering",
"line");
104 Tcl_SetChannelOption(interp, channel,
"-encoding",
"utf-8");
106 Tcl_SetStdChannel(channel, TCL_STDOUT);
119 if (!Tcl_InterpDeleted(interp)) {
120 Tcl_DeleteInterp(interp);
131 static bool scheduled =
false;
134 atexit(Tcl_Finalize);
138int Interpreter::outputProc(ClientData clientData,
const char* buf,
142 auto* output =
static_cast<Interpreter*
>(clientData)->output;
143 std::string_view text(buf, toWrite);
144 if (!text.empty() && output) {
157 return Tcl_FindCommand(interp, name.
c_str(),
nullptr, 0);
162 auto token = Tcl_CreateObjCommand(
163 interp, name.
c_str(), commandProc,
164 static_cast<ClientData
>(&command),
nullptr);
170 Tcl_DeleteCommandFromToken(interp,
static_cast<Tcl_Command
>(command.
getToken()));
173int Interpreter::commandProc(ClientData clientData, Tcl_Interp* interp,
174 int objc, Tcl_Obj*
const* objv)
177 auto& command = *
static_cast<Command*
>(clientData);
178 std::span<const TclObject> tokens(
179 std::bit_cast<TclObject*>(
const_cast<Tcl_Obj**
>(objv)),
184 if (!command.isAllowedInEmptyMachine()) {
185 if (
const auto* controller =
187 &command.getCommandController())) {
188 if (!controller->getMSXMotherBoard().getMachineConfig()) {
190 "Can't execute command in empty machine");
194 command.execute(tokens, result);
195 }
catch (MSXException& e) {
196 result =
e.getMessage();
212 return execute(
"openmsx::all_command_names");
217 return Tcl_CommandComplete(command.
c_str()) != 0;
222 if (Tcl_Eval(interp, command.
c_str()) != TCL_OK) {
225 return TclObject(Tcl_GetObjResult(interp));
230 if (Tcl_EvalFile(interp, filename.
c_str()) != TCL_OK) {
233 return TclObject(Tcl_GetObjResult(interp));
240 TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)) {
242 std::cerr << Tcl_GetStringResult(interp) <<
'\n';
245static Tcl_Obj* getVar(Tcl_Interp* interp,
const TclObject& name)
247 return Tcl_ObjGetVar2(interp, name.getTclObjectNonConst(),
nullptr,
255 TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)) {
264 TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)) {
271 Tcl_UnsetVar(interp, name, TCL_GLOBAL_ONLY);
285 if (Tcl_Obj* tclVarValue = getVar(interp, name)) {
327 uintptr_t traceID = traceCount++;
328 traces.emplace_back(Trace{traceID, &variable});
330 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
331 traceProc, std::bit_cast<ClientData>(traceID));
337 uintptr_t traceID = it->id;
341 Tcl_UntraceVar(interp, name,
342 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
343 traceProc, std::bit_cast<ClientData>(traceID));
347static BaseSetting* getTraceSetting(uintptr_t traceID)
350 return t ?
t->setting :
nullptr;
354static std::string_view removeColonColon(std::string_view s)
356 if (s.starts_with(
"::")) s.remove_prefix(2);
361char* Interpreter::traceProc(ClientData clientData, Tcl_Interp* interp,
362 const char* part1,
const char* ,
int flags)
395 auto traceID = std::bit_cast<uintptr_t>(clientData);
396 auto* variable = getTraceSetting(traceID);
397 if (!variable)
return nullptr;
399 const TclObject& part1Obj = variable->getFullNameObj();
400 assert(removeColonColon(part1) == removeColonColon(part1Obj.getString()));
402 static std::string static_string;
403 if (flags & TCL_TRACE_READS) {
405 setVar(interp, part1Obj, variable->getValue());
406 }
catch (MSXException& e) {
407 static_string = std::move(e).getMessage();
408 return const_cast<char*
>(static_string.c_str());
411 if (flags & TCL_TRACE_WRITES) {
413 Tcl_Obj* v = getVar(interp, part1Obj);
414 TclObject newValue(v ? v : Tcl_NewObj());
415 variable->setValueDirect(newValue);
416 const TclObject& newValue2 = variable->getValue();
417 if (newValue != newValue2) {
418 setVar(interp, part1Obj, newValue2);
420 }
catch (MSXException& e) {
421 setVar(interp, part1Obj, getSafeValue(*variable));
422 static_string = std::move(e).getMessage();
423 return const_cast<char*
>(static_string.c_str());
426 if (flags & TCL_TRACE_UNSETS) {
432 variable->getDefaultValue()));
433 }
catch (MSXException&) {
439 setVar(interp, part1Obj, getSafeValue(*variable));
440 Tcl_TraceVar(interp, part1, TCL_TRACE_READS |
441 TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
443 std::bit_cast<ClientData>(traceID));
464 Tcl_DoOneEvent(TCL_DONT_WAIT);
469 return {interp, command};
475 int result = Tcl_ParseCommand(interp, command.data(), narrow<int>(command.size()), 0, &parseInfo);
476 Tcl_FreeParse(&parseInfo);
477 return result == TCL_OK;
483 int result = Tcl_ParseExpr(interp, expression.data(), narrow<int>(expression.size()), &parseInfo);
484 Tcl_FreeParse(&parseInfo);
485 return result == TCL_OK;
490 assert(argc <= tokens.size());
491 Tcl_WrongNumArgs(interp, narrow<int>(argc), std::bit_cast<Tcl_Obj* const*>(tokens.data()), message);
virtual std::optional< TclObject > getOptionalValue() const =0
Like getValue(), but in case of error returns an empty optional instead of throwing an exception.
virtual void setValueDirect(const TclObject &value)=0
Similar to setValue(), but doesn't trigger Tcl traces.
const TclObject & getFullNameObj() const
Get the name of this setting.
std::string_view getFullName() const
void setToken(void *token_)
virtual void output(std::string_view text)=0
void unsetVariable(const char *name)
TclObject execute(zstring_view command)
TclObject getCommandNames()
void init(const char *programName) const
void wrongNumArgs(unsigned argc, std::span< const TclObject > tokens, const char *message)
void deleteNamespace(const std::string &name)
Delete the global namespace with given name.
bool validCommand(std::string_view command)
bool validExpression(std::string_view expression)
void registerSetting(BaseSetting &variable)
void createNamespace(const std::string &name)
Create the global namespace with given name.
void unregisterCommand(Command &command)
void setVariable(const TclObject &name, const TclObject &value)
TclParser parse(std::string_view command)
TclObject executeFile(zstring_view filename)
bool isComplete(zstring_view command) const
void unregisterSetting(BaseSetting &variable)
bool hasCommand(zstring_view name) const
void registerCommand(zstring_view name, Command &command)
Tcl_Obj * getTclObjectNonConst() const
zstring_view getString() const
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr const char * data() const
constexpr const char * c_str() const
const string & getSystemDataDir()
Get system directory.
const string & getUserDataDir()
Get the openMSX data dir in the user's home directory.
This file implemented 3 utility functions:
auto * binary_find(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
auto rfind_unguarded(RANGE &range, const VAL &val, Proj proj={})
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
TemporaryString tmpStrCat(Ts &&... ts)