16 #include <QInputDialog>
17 #include <QApplication>
18 #include <QFileDialog>
23 #include "hal_config.h"
29 #if PY_VERSION_HEX < 0x030900a0
41 :
QObject(parent), mContext(nullptr), mSender(nullptr), mTriggerReset(false), mThread(nullptr),
42 mThreadAborted(false), mInterpreterCaller(nullptr)
44 py::initialize_interpreter();
59 mMainThreadState = PyEval_SaveThread();
65 PyEval_RestoreThread(mMainThreadState);
68 py::finalize_interpreter();
88 mThreadAborted =
true;
103 std::string command =
"import __main__\n"
104 "import io, sys, threading, builtins\n";
107 command +=
"sys.path.append('" + path.string() +
"')\n";
111 "from hal_gui.console import reset\n"
112 "from hal_gui.console import clear\n"
113 "class StdOutCatcher(io.TextIOBase):\n"
114 " def __init__(self):\n"
116 " def write(self, stuff):\n"
117 " from hal_gui import console\n"
118 " if threading.current_thread() is threading.main_thread():\n"
119 " console.redirector.write_stdout(stuff)\n"
121 " console.redirector.thread_stdout(stuff)\n"
122 "class StdErrCatcher(io.TextIOBase):\n"
123 " def __init__(self):\n"
125 " def write(self, stuff):\n"
126 " from hal_gui import console\n"
127 " if threading.current_thread() is threading.main_thread():\n"
128 " console.redirector.write_stderr(stuff)\n"
130 " console.redirector.thread_stderr(stuff)\n"
131 "def redirect_input(prompt=\"Please enter input:\"):\n"
132 " if threading.current_thread() is threading.main_thread():\n"
133 " return \"input in main thread not supported\"\n"
135 " return console.redirector.thread_stdin(prompt)\n"
136 "sys.stdout = StdOutCatcher()\n"
137 "sys.__stdout__ = sys.stdout\n"
138 "sys.stderr = StdErrCatcher()\n"
139 "sys.__stderr__ = sys.stderr\n"
140 "builtins.input = redirect_input\n"
141 "builtins.raw_input = redirect_input\n"
144 py::exec(command, *context, *context);
158 std::string command =
"import __main__\n"
159 "import io, sys, threading\n"
160 "from hal_gui.console import reset\n"
161 "from hal_gui.console import clear\n"
164 py::exec(command, *context, *context);
182 mContext =
new py::dict(**py::globals());
185 (*mContext)[
"console"] = py::module::import(
"hal_gui.console");
186 (*mContext)[
"hal_gui"] = py::module::import(
"hal_gui");
203 if (
input ==
"quit()")
205 forwardError(
"quit() cannot be used in this interpreter. Use console.reset() to restart it.\n");
209 if (
input ==
"help()")
211 forwardError(
"help() cannot be used in this interpreter.\n");
215 if (
input ==
"license()")
217 forwardError(
"license() cannot be used in this interpreter.\n");
223 forwardError(
"python thread already running. Please retry after other thread finished.\n");
226 log_info(
"python",
"Python console execute: \"{}\".",
input.toStdString());
228 mInterpreterCaller = caller;
229 startThread(
input,!multiple_expressions);
232 void PythonContext::startThread(
const QString& input,
bool singleStatement)
251 log_info(
"python",
"Python console execute: \"{}\".",
input.toStdString());
255 PyGILState_STATE
state = PyGILState_Ensure();
265 rc = py::eval<py::eval_single_statement>(
input.toStdString(), *mContext, *mContext);
272 catch (py::error_already_set& e)
274 qDebug() <<
"interpret error set";
279 catch (std::exception& e)
281 qDebug() <<
"interpret exception";
286 PyGILState_Release(
state);
295 log_warning(
"python",
"Not executed, python script already running");
301 log_info(
"UserStudy",
"Python editor execute script:\n{}\n",
input.toStdString());
307 mInterpreterCaller = caller;
308 startThread(
input,
false);
311 void PythonContext::handleScriptOutput(
const QString& txt)
317 void PythonContext::handleScriptError(
const QString& txt)
323 void PythonContext::handleInputRequired(
int type,
const QString& prompt,
const QVariant &defaultValue)
339 if (!confirm) userInput.
clear();
340 if (mThread) mThread->
setInput(userInput);
346 if (mThread) mThread->
setInput(userInput);
354 PythonGateSelectionReceiver* pgs =
new PythonGateSelectionReceiver(mThread,
this);
355 GateDialog gd(gats, prompt, pgs, qApp->activeWindow());
359 if (!gd.pickerModeActivated() && mThread) mThread->
setInput(QVariant::fromValue<void*>(gatSelect));
366 PythonModuleSelectionReceiver* pms =
new PythonModuleSelectionReceiver(mThread,
this);
367 ModuleDialog md({}, prompt,
false, pms, qApp->activeWindow());
371 if (!md.pickerModeActivated() && mThread) mThread->
setInput(QVariant::fromValue<void*>(modSelect));
377 if (mThread) mThread->
setInput(userInput);
390 if (mThread) mThread->
setInput(QVariant::fromValue<void*>(gatSelect));
399 if (mThread) mThread->
setInput(QVariant::fromValue<void*>(modSelect));
403 void PythonContext::handleConsoleInputReceived(
const QString& input)
410 void PythonContext::handleThreadFinished()
413 PythonEditor* calledFromEditor =
dynamic_cast<PythonEditor*
>(mInterpreterCaller);
417 mThreadAborted =
false;
431 mThreadAborted =
false;
436 delete mLayoutLocker;
441 if (calledFromEditor)
443 calledFromEditor->handleThreadFinished();
446 forwardError(
"\nreset() can only be used in the Python console!\n");
447 mTriggerReset =
false;
451 forwardError(
"\nclear() can only be used in the Python console!\n");
452 mTriggerClear =
false;
492 PyGILState_STATE
state = PyGILState_Ensure();
494 std::vector<std::tuple<std::string, std::string>> ret_val;
497 auto namespaces = py::list();
498 if (use_console_context)
500 namespaces.append(*mContext);
501 namespaces.append(*mContext);
505 py::dict tmp_context = py::globals();
507 namespaces.append(tmp_context);
508 namespaces.append(tmp_context);
510 auto jedi = py::module::import(
"jedi");
511 py::object script = jedi.attr(
"Interpreter")(text.
toStdString(), namespaces);
513 if (py::hasattr(script,
"complete"))
514 list = script.attr(
"complete")();
515 else if (py::hasattr(script,
"completions"))
516 list = script.attr(
"completions")();
518 log_warning(
"python",
"Jedi autocompletion failed, neither complete() nor completions() found.");
520 for (
const auto& entry : list)
522 auto a = entry.attr(
"name_with_symbols").cast<std::string>();
523 auto b = entry.attr(
"complete").cast<std::string>();
524 ret_val.emplace_back(a, b);
527 catch (py::error_already_set& e)
534 PyGILState_Release(
state);
542 PyGILState_STATE
state = PyGILState_Ensure();
544 #if PY_VERSION_HEX < 0x030900a0
553 if (e.error == E_EOF)
555 PyGILState_Release(
state);
558 PyGILState_Release(
state);
563 PyGILState_Release(
state);
569 PyCompilerFlags flags {PyCF_ONLY_AST, PY_MINOR_VERSION};
570 PyObject* o = Py_CompileStringExFlags(text.
toStdString().c_str(),
"stdin", Py_file_input, &flags, 0);
573 PyGILState_Release(
state);
579 void PythonContext::handleReset()
583 PyGILState_STATE
state = PyGILState_Ensure();
586 PyGILState_Release(state);
588 mTriggerReset =
false;
592 void PythonContext::handleClear()
600 mTriggerClear =
false;
606 mTriggerReset =
true;
611 mTriggerClear =
true;
616 PyGILState_STATE
state = PyGILState_Ensure();
618 PyGILState_Release(
state);
const std::vector< Gate * > & get_gates() const
Gate * get_gate_by_id(const u32 gate_id) const
Module * get_module_by_id(u32 module_id) const
An interactable python console.
void handleThreadFinished()
void setInputMode(bool state)
void inputReceived(QString input)
virtual void clear() override
virtual void handleStdout(const QString &output) override
virtual void handleError(const QString &output) override
std::vector< std::tuple< std::string, std::string > > complete(const QString &text, bool use_console_context)
PythonContext(QObject *parent=nullptr)
int checkCompleteStatement(const QString &text)
PythonThread * pythonThread() const
static void initializeContext(py::dict *context)
void interpretBackground(QObject *caller, const QString &input, bool multiple_expressions=false)
void setConsole(PythonConsole *console)
void interpretScript(QObject *caller, const QString &input)
void forwardError(const QString &output)
void interpretForeground(const QString &input)
void abortThreadAndWait()
void forwardStdout(const QString &output)
static void initializeScript(py::dict *context)
void handleGatesPicked(const QSet< u32 > &gats) override
void handleModulesPicked(const QSet< u32 > &gats) override
void stdError(QString txt)
QString errorMessage() const
void setInput(const QVariant &inp)
void stdOutput(QString txt)
void requireInput(int type, QString prompt, QVariant defaultValue)
const QString & stdoutBuffer() const
int numberSelectedGates() const
int numberSelectedModules() const
#define log_error(channel,...)
#define log_info(channel,...)
#define log_warning(channel,...)
T rtrim(const T &s, const char *to_remove=" \t\r\n")
std::filesystem::path get_library_directory()
std::vector< std::filesystem::path > get_plugin_directories()
SelectionRelay * gSelectionRelay
std::shared_ptr< Netlist > gNetlistOwner
grammar _PyParser_Grammar
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QSet::const_iterator constBegin() const const
QSet::iterator insert(const T &value)
bool isEmpty() const const
QString fromStdString(const std::string &str)
bool isEmpty() const const
std::string toStdString() const const
void setReadOnly(bool ro)
void start(QThread::Priority priority)
bool wait(QDeadlineTimer deadline)
int toInt(bool *ok) const const
QString toString() const const