diff --git a/3rdparty/python-console/CMakeLists.txt b/3rdparty/python-console/CMakeLists.txt new file mode 100644 index 00000000..dd4d6047 --- /dev/null +++ b/3rdparty/python-console/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required( VERSION 2.8 ) +project( PythonInterpreter ) + +find_package( Qt4 REQUIRED ) +include( ${QT_USE_FILE} ) +find_package( PythonLibs REQUIRED ) +include_directories( ${PYTHON_INCLUDE_DIRS} ) + +add_executable( test_python_interpreter test_python_interpreter.cpp Interpreter.cpp ) +target_link_libraries( test_python_interpreter ${PYTHON_LIBRARIES} ) + +qt4_wrap_cpp( Console_MOC Console.h ) +add_executable( test_console test_console.cpp + Console.cpp ${Console_MOC} + ColumnFormatter.cpp + Interpreter.cpp + ParseHelper.cpp + ParseHelper.BlockParseState.cpp + ParseHelper.BracketParseState.cpp + ParseHelper.ContinuationParseState.cpp + ParseMessage.cpp +) +target_link_libraries( test_console ${QT_LIBRARIES} ${PYTHON_LIBRARIES} ) + +add_executable( test_parse_helper test_parse_helper.cpp + ParseHelper.cpp + ParseHelper.BlockParseState.cpp + ParseHelper.ContinuationParseState.cpp + ParseListener.cpp + ParseMessage.cpp +) + +add_executable( test_cli test_cli.cpp + ParseHelper.cpp + ParseHelper.BlockParseState.cpp + ParseHelper.ContinuationParseState.cpp + ParseListener.cpp + ParseMessage.cpp + Interpreter.cpp +) +target_link_libraries( test_cli + ${PYTHON_LIBRARIES} +) diff --git a/3rdparty/python-console/ColumnFormatter.cpp b/3rdparty/python-console/ColumnFormatter.cpp new file mode 100644 index 00000000..adbda96a --- /dev/null +++ b/3rdparty/python-console/ColumnFormatter.cpp @@ -0,0 +1,141 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "ColumnFormatter.h" +#include +#include +#include +#include +#include +#include +#include +#include + +bool ColumnFormatter::load(const std::string& fn) +{ + items.clear(); + std::ifstream ifs(fn.c_str()); + if (!ifs.is_open()) + return false; + std::string str; + while (!ifs.eof()) + { + std::getline( ifs, str ); + if (!ifs.eof()) + items.push_back(str); + } + return true; +} + +int ColumnFormatter::solve(int width) +{ + bool fits = true; + int i = 1; + while (fits) + { + ++i; + std::vector widths = divideItems(i); + int columnWidth = width / i; + for (int j = 0; j < widths.size(); ++j) + { + fits &= (widths[j] < columnWidth); + } + if (!fits) + { + --i; + } + } + return i; +} + +std::vector ColumnFormatter::divideItems(int numColumns) +{ + columns.clear(); + for (int i = 0; i < numColumns; ++i) + columns.push_back(std::list()); + for (int i = 0; i < items.size(); ++i) + { + columns[i % numColumns].push_back(items[i]); + } + + // count the fattest item in each column + std::vector res(numColumns); + for (int i = 0; i < numColumns; ++i) + { + for (std::list::const_iterator it = + columns[i].begin(); it != columns[i].end(); ++it) + { + if (res[i] < it->size()) + res[i] = it->size(); + } + } + + return res; +} + +void ColumnFormatter::format(int width) +{ + m_formattedOutput.clear(); + int cols = solve(width); + std::vector colWidths(cols, width / cols); + int rem = width % cols; + for (int i = 0; i < rem; ++i) + { + colWidths[i]++; + } + divideItems(cols); + std::vector< std::list::const_iterator > its; + std::vector< std::list::const_iterator > it_ends; + for (int i = 0; i < columns.size(); ++i) + { + its.push_back(columns[i].begin()); + it_ends.push_back(columns[i].end()); + } + bool done = false; + while (!done) + { + std::stringstream row_ss; + for (int i = 0; i < columns.size(); ++i) + { + std::stringstream item_ss; + std::string item; + if (its[i] != it_ends[i]) + { + item = *its[i]; + ++its[i]; + } + item_ss << std::left << std::setw(colWidths[i]) << item; + row_ss << item_ss.str(); + } + m_formattedOutput.push_back(row_ss.str()); + + done = true; + for (int i = 0; i < columns.size(); ++i) + { + done &= (its[i] == it_ends[i]); + } + } +} + +const std::list& ColumnFormatter::formattedOutput() const +{ + return m_formattedOutput; +} diff --git a/3rdparty/python-console/ColumnFormatter.h b/3rdparty/python-console/ColumnFormatter.h new file mode 100644 index 00000000..4fedf2ea --- /dev/null +++ b/3rdparty/python-console/ColumnFormatter.h @@ -0,0 +1,77 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#ifndef COLUMN_FORMATTER_H +#define COLUMN_FORMATTER_H +#include +#include +#include + +/** +Format a list of items into as many columns as width permits. +*/ +class ColumnFormatter +{ +protected: + std::vector items; + std::vector< std::list > columns; + std::list< std::string > m_formattedOutput; + +public: + /** + Load items from file, one item per line. + */ + bool load(const std::string& fn); + + template + void setItems(InputIterator begin, InputIterator end) + { + items.clear(); + for (InputIterator it = begin; it != end; ++it) + { + items.push_back(*it); + } + } + + /** + Determine the number of columns that the items can be into, given a + width limit. + */ + int solve(int width); + + /** + Divide items into numColumns. + */ + std::vector divideItems(int numColumns); + + /** + Generate formatted output, the items formatted into as many columns as can + be fit in width. + */ + void format(int width); + + /** + Get output. + */ + const std::list& formattedOutput() const; +}; + +#endif // COLUMN_FORMATTER_H diff --git a/3rdparty/python-console/Console.cpp b/3rdparty/python-console/Console.cpp new file mode 100644 index 00000000..f9413542 --- /dev/null +++ b/3rdparty/python-console/Console.cpp @@ -0,0 +1,297 @@ +#include "Console.h" +#include "Interpreter.h" +#include "ColumnFormatter.h" + +#include +#include +#include + +#include "Utils.h" + +const QString Console::PROMPT = ">>> "; +const QString Console::MULTILINE_PROMPT = "... "; +const QColor Console::NORMAL_COLOR = QColor::fromRgbF( 0, 0, 0 ); +const QColor Console::ERROR_COLOR = QColor::fromRgbF( 1.0, 0, 0 ); +const QColor Console::OUTPUT_COLOR = QColor::fromRgbF( 0, 0, 1.0 ); + +Console::Console( QWidget* parent ): + QTextEdit( parent ), + m_interpreter( new Interpreter ) +{ + QFont font; + font.setFamily("Courier New"); + setFont(font); + m_parseHelper.subscribe( this ); + displayPrompt( ); +} + +Console::~Console( ) +{ + delete m_interpreter; +} + +void Console::keyPressEvent( QKeyEvent* e ) +{ + switch ( e->key() ) + { + case Qt::Key_Return: + handleReturnKeyPress( ); + return; + + case Qt::Key_Tab: + autocomplete( ); + return; + + case Qt::Key_Backspace: + if ( ! canBackspace( ) ) + return; + break; + + case Qt::Key_Up: + previousHistory( ); + return; + + case Qt::Key_Down: + nextHistory( ); + return; + + case Qt::Key_Left: + if ( ! canGoLeft( ) ) + return; + } + + QTextEdit::keyPressEvent( e ); +} + +void Console::handleReturnKeyPress( ) +{ + if ( ! cursorIsOnInputLine( ) ) + { + return; + } + + QString line = getLine( ); + +#ifndef NDEBUG + std::cout << line.toStdString( ) << "\n"; +#endif + + m_parseHelper.process( line.toStdString( ) ); + if ( m_parseHelper.buffered( ) ) + { + append(""); + displayPrompt( ); + } + if ( line.size( ) ) + { + m_historyBuffer.push_back( line.toStdString( ) ); + m_historyIt = m_historyBuffer.end(); + } + moveCursorToEnd( ); +} + +void Console::parseEvent( const ParseMessage& message ) +{ + // handle invalid user input + if ( message.errorCode ) + { + setTextColor( ERROR_COLOR ); + append(message.message.c_str()); + + setTextColor( NORMAL_COLOR ); + append(""); + displayPrompt( ); + return; + } + + // interpret valid user input + int errorCode; + std::string res; + if ( message.message.size() ) + res = m_interpreter->interpret( message.message, &errorCode ); + if ( errorCode ) + { + setTextColor( ERROR_COLOR ); + } + else + { + setTextColor( OUTPUT_COLOR ); + } + + if ( res.size( ) ) + { + append(res.c_str()); + } + + setTextColor( NORMAL_COLOR ); + + // set up the next line on the console + append(""); + displayPrompt( ); +} + +QString Console::getLine( ) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::StartOfLine ); + cursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, Console::PROMPT.size( ) ); + cursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor ); + QString line = cursor.selectedText( ); + cursor.clearSelection( ); + return line; +} + +bool Console::cursorIsOnInputLine( ) +{ + int cursorBlock = textCursor( ).blockNumber( ); + QTextCursor bottomCursor = textCursor( ); + bottomCursor.movePosition( QTextCursor::End ); + int bottomBlock = bottomCursor.blockNumber( ); + return ( cursorBlock == bottomBlock ); +} + +bool Console::inputLineIsEmpty( ) +{ + QTextCursor bottomCursor = textCursor( ); + bottomCursor.movePosition( QTextCursor::End ); + int col = bottomCursor.columnNumber( ); + return ( col == Console::PROMPT.size( ) ); +} + +bool Console::canBackspace( ) +{ + if ( ! cursorIsOnInputLine( ) ) + { + return false; + } + + if ( inputLineIsEmpty( ) ) + { + return false; + } + + return true; +} + +bool Console::canGoLeft( ) +{ + if ( cursorIsOnInputLine( ) ) + { + QTextCursor bottomCursor = textCursor( ); + int col = bottomCursor.columnNumber( ); + return (col > Console::PROMPT.size( )); + } + return true; +} + +void Console::displayPrompt( ) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::End ); + if ( m_parseHelper.buffered( ) ) + { + cursor.insertText( Console::MULTILINE_PROMPT ); + } + else + { + cursor.insertText( Console::PROMPT ); + } + cursor.movePosition( QTextCursor::EndOfLine ); +} + +void Console::autocomplete( ) +{ + if ( ! cursorIsOnInputLine( ) ) + return; + + QString line = getLine( ); + const std::list& suggestions = + m_interpreter->suggest( line.toStdString( ) ); + if (suggestions.size() == 1) + { + line = suggestions.back().c_str(); + } + else + { + // try to complete to longest common prefix + std::string prefix = + LongestCommonPrefix(suggestions.begin(), suggestions.end()); + if (prefix.size() > line.size()) + { + line = prefix.c_str(); + } + else + { + ColumnFormatter fmt; + fmt.setItems(suggestions.begin(), suggestions.end()); + fmt.format(width() / 10); + setTextColor( OUTPUT_COLOR ); + const std::list& formatted = fmt.formattedOutput(); + for (std::list::const_iterator it = formatted.begin(); + it != formatted.end(); ++it) + { + append(it->c_str()); + } + std::cout << width() << "\n"; + setTextColor( NORMAL_COLOR ); + } + } + + // set up the next line on the console + append(""); + displayPrompt( ); + moveCursorToEnd( ); + cursor.insertText( line ); + moveCursorToEnd( ); +} + +void Console::previousHistory( ) +{ + if ( ! cursorIsOnInputLine( ) ) + return; + + if ( ! m_historyBuffer.size( ) ) + return; + + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::StartOfLine ); + cursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, Console::PROMPT.size( ) ); + cursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor ); + cursor.removeSelectedText( ); + if ( m_historyIt != m_historyBuffer.begin( ) ) + { + --m_historyIt; + } + cursor.insertText( m_historyIt->c_str() ); +} + +void Console::nextHistory( ) +{ + if ( ! cursorIsOnInputLine( ) ) + return; + + if ( ! m_historyBuffer.size( ) ) + return; + if ( m_historyIt == m_historyBuffer.end( ) ) + { + return; + } + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::StartOfLine ); + cursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, Console::PROMPT.size( ) ); + cursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor ); + cursor.removeSelectedText( ); + ++m_historyIt; + if ( m_historyIt == m_historyBuffer.end( ) ) + { + return; + } + cursor.insertText( m_historyIt->c_str() ); +} + +void Console::moveCursorToEnd( ) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::End ); + setTextCursor( cursor ); +} diff --git a/3rdparty/python-console/Console.h b/3rdparty/python-console/Console.h new file mode 100644 index 00000000..e4a0730c --- /dev/null +++ b/3rdparty/python-console/Console.h @@ -0,0 +1,78 @@ +/** +python-console +Copyright (C) 2018 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef ATSUI_CONSOLE_H +#define ATSUI_CONSOLE_H +#include +#include +#include "ParseHelper.h" +#include "ParseListener.h" + +class QWidget; +class QKeyEvent; +class Interpreter; + +class Console : public QTextEdit, ParseListener +{ + Q_OBJECT + +public: + Console( QWidget* parent = 0 ); + virtual ~Console( ); + +protected: + // override QTextEdit + virtual void keyPressEvent( QKeyEvent* e ); + + virtual void handleReturnKeyPress( ); + + /** + Handle a compilable chunk of Python user input. + */ + virtual void parseEvent( const ParseMessage& message ); + + QString getLine( ); + bool cursorIsOnInputLine( ); + bool inputLineIsEmpty( ); + bool canBackspace( ); + bool canGoLeft( ); + void displayPrompt( ); + void autocomplete( ); + void previousHistory( ); + void nextHistory( ); + void moveCursorToEnd( ); + + static const QString PROMPT; + static const QString MULTILINE_PROMPT; + + static const QColor NORMAL_COLOR; + static const QColor ERROR_COLOR; + static const QColor OUTPUT_COLOR; + + Interpreter* m_interpreter; + ParseHelper m_parseHelper; + std::list m_historyBuffer; + std::list::const_iterator m_historyIt; +}; + +#endif // ATSUI_CONSOLE_H diff --git a/3rdparty/python-console/Interpreter.cpp b/3rdparty/python-console/Interpreter.cpp new file mode 100644 index 00000000..95615ed2 --- /dev/null +++ b/3rdparty/python-console/Interpreter.cpp @@ -0,0 +1,231 @@ +#include "Interpreter.h" +#include +#include +#include + +PyThreadState* Interpreter::MainThreadState = NULL; + +Interpreter::Interpreter( ) +{ + PyEval_AcquireLock( ); + m_threadState = Py_NewInterpreter( ); + + PyObject *module = PyImport_ImportModule("__main__"); + loc = glb = PyModule_GetDict(module); + SetupRedirector( m_threadState ); + PyRun_SimpleString("import sys\n" + "import redirector\n" + "sys.path.insert(0, \".\")\n" // add current path + "sys.stdout = redirector.redirector()\n" + "sys.stderr = sys.stdout\n" + "import rlcompleter\n" + "sys.completer = rlcompleter.Completer()\n" + ); + + PyEval_ReleaseThread( m_threadState ); +} + +Interpreter::~Interpreter( ) +{ +#ifndef NDEBUG + std::cout << "delete interpreter\n"; +#endif + PyEval_AcquireThread( m_threadState ); + Py_EndInterpreter( m_threadState ); + PyEval_ReleaseLock( ); +} + +void +Interpreter::test( ) +{ + PyEval_AcquireThread( m_threadState ); + + PyObject* py_result; + PyObject* dum; + std::string command = "print 'Hello world'\n"; + py_result = Py_CompileString(command.c_str(), "", Py_single_input); + if ( py_result == 0 ) + { + std::cout << "Huh?\n"; + PyEval_ReleaseThread( m_threadState ); + return; + } + dum = PyEval_EvalCode ((PyCodeObject *)py_result, glb, loc); + Py_XDECREF (dum); + Py_XDECREF (py_result); + + std::cout << GetResultString( m_threadState ); + GetResultString( m_threadState ) = ""; + + PyEval_ReleaseThread( m_threadState ); +} + +std::string +Interpreter::interpret( const std::string& command, int* errorCode ) +{ + PyEval_AcquireThread( m_threadState ); + *errorCode = 0; + + PyObject* py_result; + PyObject* dum; + std::string res; +#ifndef NDEBUG + std::cout << "interpreting (" << command << ")\n"; +#endif + py_result = Py_CompileString(command.c_str(), "", Py_single_input); + if ( py_result == 0 ) + { +#ifndef NDEBUG + std::cout << "Huh?\n"; +#endif + if ( PyErr_Occurred( ) ) + { + *errorCode = 1; + PyErr_Print( ); + res = GetResultString( m_threadState ); + GetResultString( m_threadState ) = ""; + } + + PyEval_ReleaseThread( m_threadState ); + return res; + } + dum = PyEval_EvalCode ((PyCodeObject *)py_result, glb, loc); + Py_XDECREF (dum); + Py_XDECREF (py_result); + if ( PyErr_Occurred( ) ) + { + *errorCode = 1; + PyErr_Print( ); + } + + res = GetResultString( m_threadState ); + GetResultString( m_threadState ) = ""; + + PyEval_ReleaseThread( m_threadState ); + return res; +} + +const std::list& Interpreter::suggest( const std::string& hint ) +{ + PyEval_AcquireThread( m_threadState ); + m_suggestions.clear(); + int i = 0; + std::string command = boost::str( + boost::format("sys.completer.complete('%1%', %2%)\n") + % hint + % i); +#ifndef NDEBUG + std::cout << command << "\n"; +#endif + std::string res; + do + { + PyObject* py_result; + PyObject* dum; + py_result = Py_CompileString(command.c_str(), "", Py_single_input); + dum = PyEval_EvalCode ((PyCodeObject *)py_result, glb, loc); + Py_XDECREF (dum); + Py_XDECREF (py_result); + res = GetResultString( m_threadState ); + GetResultString( m_threadState ) = ""; + ++i; + command = boost::str( + boost::format("sys.completer.complete('%1%', %2%)\n") + % hint + % i); + if (res.size()) + { + // throw away the newline + res = res.substr(1, res.size() - 3); + m_suggestions.push_back(res); + } + } + while (res.size()); + + PyEval_ReleaseThread( m_threadState ); + return m_suggestions; +} + +void +Interpreter::Initialize( ) +{ + Py_Initialize( ); + PyEval_InitThreads( ); + MainThreadState = PyEval_SaveThread( ); +} + +void +Interpreter::Finalize( ) +{ + PyEval_RestoreThread( MainThreadState ); + Py_Finalize( ); +} + +std::string& Interpreter::GetResultString( PyThreadState* threadState ) +{ + static std::map< PyThreadState*, std::string > ResultStrings; + if ( !ResultStrings.count( threadState ) ) + { + ResultStrings[ threadState ] = ""; + } + return ResultStrings[ threadState ]; +} + +PyObject* Interpreter::RedirectorInit(PyObject *, PyObject *) +{ + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* Interpreter::RedirectorWrite(PyObject *, PyObject *args) +{ + char* output; + PyObject *selfi; + + if (!PyArg_ParseTuple(args,"Os",&selfi,&output)) + { + return NULL; + } + + std::string outputString( output ); + PyThreadState* currentThread = PyThreadState_Get( ); + std::string& resultString = GetResultString( currentThread ); + resultString = resultString + outputString; + Py_INCREF(Py_None); + return Py_None; +} + +PyMethodDef Interpreter::ModuleMethods[] = { {NULL,NULL,0,NULL} }; +PyMethodDef Interpreter::RedirectorMethods[] = +{ + {"__init__", Interpreter::RedirectorInit, METH_VARARGS, + "initialize the stdout/err redirector"}, + {"write", Interpreter::RedirectorWrite, METH_VARARGS, + "implement the write method to redirect stdout/err"}, + {NULL,NULL,0,NULL}, +}; + +void Interpreter::SetupRedirector( PyThreadState* threadState ) +{ + PyMethodDef *def; + + /* create a new module and class */ + PyObject *module = Py_InitModule("redirector", ModuleMethods); + PyObject *moduleDict = PyModule_GetDict(module); + PyObject *classDict = PyDict_New(); + PyObject *className = PyString_FromString("redirector"); + PyObject *fooClass = PyClass_New(NULL, classDict, className); + PyDict_SetItemString(moduleDict, "redirector", fooClass); + Py_DECREF(classDict); + Py_DECREF(className); + Py_DECREF(fooClass); + + /* add methods to class */ + for (def = RedirectorMethods; def->ml_name != NULL; def++) { + PyObject *func = PyCFunction_New(def, NULL); + PyObject *method = PyMethod_New(func, NULL, fooClass); + PyDict_SetItemString(classDict, def->ml_name, method); + Py_DECREF(func); + Py_DECREF(method); + } +} diff --git a/3rdparty/python-console/Interpreter.h b/3rdparty/python-console/Interpreter.h new file mode 100644 index 00000000..9e371179 --- /dev/null +++ b/3rdparty/python-console/Interpreter.h @@ -0,0 +1,97 @@ +/** +python-console +Copyright (C) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef INTERPRETER_H +#define INTERPRETER_H +#include +#include +#include +#if defined(__APPLE__) && defined(__MACH__) +/* + * The following undefs for C standard library macros prevent + * build errors of the following type on mac ox 10.7.4 and XCode 4.3.3 + * +/usr/include/c++/4.2.1/bits/localefwd.h:57:21: error: too many arguments provided to function-like macro invocation + isspace(_CharT, const locale&); + ^ +/usr/include/c++/4.2.1/bits/localefwd.h:56:5: error: 'inline' can only appear on functions + inline bool + ^ +/usr/include/c++/4.2.1/bits/localefwd.h:57:5: error: variable 'isspace' declared as a template + isspace(_CharT, const locale&); + ^ +*/ +#undef isspace +#undef isupper +#undef islower +#undef isalpha +#undef isalnum +#undef toupper +#undef tolower +#endif + +/** +Wraps a Python interpreter, which you can pass commands as strings to interpret +and get strings of output/error in return. +*/ +class Interpreter +{ +protected: + static PyThreadState* MainThreadState; + + PyThreadState* m_threadState; + PyObject* glb; + PyObject* loc; + + std::list< std::string > m_suggestions; + +public: + /** + Instantiate a Python interpreter. + */ + Interpreter( ); + virtual ~Interpreter( ); + + void test( ); + std::string interpret( const std::string& command, int* errorCode ); + const std::list& suggest( const std::string& hint ); + + /** + Call this before constructing and using Interpreter. + */ + static void Initialize( ); + + /** + Call this when done using Interpreter. + */ + static void Finalize( ); + +protected: + static void SetupRedirector( PyThreadState* threadState ); + static PyObject* RedirectorInit(PyObject *, PyObject *); + static PyObject* RedirectorWrite(PyObject *, PyObject *args); + static std::string& GetResultString( PyThreadState* threadState ); + static PyMethodDef ModuleMethods[]; + static PyMethodDef RedirectorMethods[]; +}; +#endif // INTERPRETER_H diff --git a/3rdparty/python-console/LICENSE b/3rdparty/python-console/LICENSE new file mode 100644 index 00000000..c53c2635 --- /dev/null +++ b/3rdparty/python-console/LICENSE @@ -0,0 +1,19 @@ +Copyright 2018 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/3rdparty/python-console/ParseHelper.BlockParseState.cpp b/3rdparty/python-console/ParseHelper.BlockParseState.cpp new file mode 100644 index 00000000..4dc568b2 --- /dev/null +++ b/3rdparty/python-console/ParseHelper.BlockParseState.cpp @@ -0,0 +1,174 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "ParseHelper.h" + +ParseHelper::BlockParseState:: +BlockParseState( ParseHelper& parent ): + ParseState( parent ) +{ } + +ParseHelper::BlockParseState:: +BlockParseState( ParseHelper& parent, const std::string& indent_ ): + ParseState( parent ), + indent( indent_ ) +{ } + +bool ParseHelper::BlockParseState:: +process(const std::string& str) +{ + bool ok = initializeIndent(str); + if ( ! ok ) + { + // finish processing + return true; + } + + Indent ind; + bool isIndented = PeekIndent( str, &ind ); + if ( isIndented ) + { +#ifndef NDEBUG + std::cout << "current line indent: "; + print( ind ); +#endif + // check if indent matches + if ( ind.Token != indent.Token ) + { + // dedent until we match or empty the stack + bool found = false; + while ( !found ) + { + parent.stateStack.pop_back( ); + if ( !parent.stateStack.size( ) ) + break; + boost::shared_ptr parseState = + boost::dynamic_pointer_cast( + parent.stateStack.back( )); + found = ( ind.Token == parseState->indent.Token ); + } + + if ( ! found ) + { +#ifndef NDEBUG + std::cout << "indent mismatch\n"; +#endif + parent.reset( ); + ParseMessage msg( 1, "IndentationError: unexpected indent"); + parent.broadcast( msg ); + return true; + } + } + + // process command + + // enter indented block state + if ( str[str.size()-1] == ':' ) + { + parent.commandBuffer.push_back( str ); + //parent.inBlock = (boost::dynamic_pointer_cast( + // parent.stateStack.back())); + + //expectingIndent = true; + boost::shared_ptr parseState( + new BlockParseState( parent ) ); + parent.stateStack.push_back( parseState ); + return true; + } + + if ( str[str.size()-1] == '\\' ) + { + parent.commandBuffer.push_back( str ); + boost::shared_ptr parseState( + new ContinuationParseState( parent ) ); + parent.stateStack.push_back( parseState ); + return true; + } + + if (BracketParseState::HasOpenBrackets( str )) + { + // FIXME: Every parse state should have its own local buffer + boost::shared_ptr parseState( + new BracketParseState( parent, str ) ); + parent.stateStack.push_back( parseState ); + return true; + } + + parent.commandBuffer.push_back( str ); + return true; + } + else + { + if ( str.size() ) + { + { +#ifndef NDEBUG + std::cout << "Expected indented block\n"; +#endif + parent.reset( ); + ParseMessage msg( 1, "IndentationError: expected an indented block" ); + parent.broadcast( msg ); + return false; + } + } +#ifndef NDEBUG + std::cout << "Leaving block\n"; +#endif + parent.stateStack.pop_back(); + parent.flush( ); + parent.reset( ); + return true; + } +} + +bool ParseHelper::BlockParseState:: +initializeIndent(const std::string& str) +{ + bool expectingIndent = (indent.Token == ""); + if ( !expectingIndent ) + { + std::cout << "already initialized indent: "; + print( indent ); + return true; + } + + Indent ind; + bool isIndented = parent.PeekIndent( str, &ind ); + if ( !isIndented ) + { +#ifndef NDEBUG + std::cout << "Expected indented block\n"; +#endif + parent.reset( ); + ParseMessage msg( 1, "IndentationError: expected an indented block" ); + parent.broadcast( msg ); + return false; + } + indent = ind; + //parent.currentIndent = ind; + //parent.indentStack.push_back( parent.currentIndent ); + //parent.expectingIndent = false; +#ifndef NDEBUG + std::cout << "initializing indent: "; + print( ind ); +#endif + return true; +} diff --git a/3rdparty/python-console/ParseHelper.BracketParseState.cpp b/3rdparty/python-console/ParseHelper.BracketParseState.cpp new file mode 100644 index 00000000..c884c77d --- /dev/null +++ b/3rdparty/python-console/ParseHelper.BracketParseState.cpp @@ -0,0 +1,127 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "ParseHelper.h" +#include +#include +const std::string ParseHelper::BracketParseState::OpeningBrackets = "[({"; +const std::string ParseHelper::BracketParseState::ClosingBrackets = "])}"; + +bool ParseHelper::BracketParseState::HasOpenBrackets(const std::string& str) +{ + std::list brackets; + bool hasOpenBrackets = LoadBrackets(str, &brackets); + return hasOpenBrackets; +} + +bool ParseHelper::BracketParseState::LoadBrackets(const std::string& str, + std::list* stack) +{ + if ( !stack ) + return false; + + stack->clear(); + for (int i = 0; i < str.size(); ++i) + { + if (OpeningBrackets.find_first_of(str[i]) != std::string::npos) + { + stack->push_back(str[i]); + } + else + { + size_t t = ClosingBrackets.find_first_of(str[i]); + if (t != std::string::npos) + { + if (t != OpeningBrackets.find_first_of(stack->back())) + return false; + stack->pop_back(); + } + } + } + return stack->size(); +} + +ParseHelper::BracketParseState::BracketParseState( ParseHelper& parent, const std::string& firstLine ): + ParseState( parent ) +{ + bool hasOpenBrackets = LoadBrackets( firstLine, &brackets ); + assert( hasOpenBrackets ); + m_buffer.push_back( firstLine ); +} + +bool ParseHelper::BracketParseState::process(const std::string& str) +{ +#ifndef NDEBUG + std::cout << "(BracketParseState) processing " << str << "\n"; +#endif + // update brackets stack + for (int i = 0; i < str.size(); ++i) + { + if (OpeningBrackets.find_first_of(str[i]) != std::string::npos) + { +#ifndef NDEBUG + std::cout << "push " << str[i] << "\n"; +#endif + brackets.push_back(str[i]); + } + else + { + size_t t = ClosingBrackets.find_first_of(str[i]); + if (t != std::string::npos) + { +#ifndef NDEBUG + std::cout << "pop " << str[i] << "\n"; +#endif + // reset state if unmatched brackets seen + if (t != OpeningBrackets.find_first_of(brackets.back())) + { + parent.reset( ); + ParseMessage msg(1, "Invalid syntax"); + parent.broadcast( msg ); + return true; + } + brackets.pop_back(); + } + } + } + + // if we've cleared the stack, we've finished accepting input here + if (!brackets.size()) + { + // squash buffered lines and place it on parent::commandBuffer + std::stringstream ss; + for (std::list::const_iterator it = m_buffer.begin(); + it != m_buffer.end(); ++it ) + { + ss << *it << "\n"; + } + ss << str; + parent.commandBuffer.push_back(ss.str()); + parent.stateStack.pop_back( ); + return false; + } + else + { + // buffer and expect more lines + m_buffer.push_back( str ); + return true; + } +} diff --git a/3rdparty/python-console/ParseHelper.ContinuationParseState.cpp b/3rdparty/python-console/ParseHelper.ContinuationParseState.cpp new file mode 100644 index 00000000..6f84d291 --- /dev/null +++ b/3rdparty/python-console/ParseHelper.ContinuationParseState.cpp @@ -0,0 +1,61 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "ParseHelper.h" +#include +#include + +ParseHelper::ContinuationParseState:: +ContinuationParseState( ParseHelper& parent_ ): + ParseState( parent_ ) +{ } + +bool ParseHelper::ContinuationParseState:: +process( const std::string& str ) +{ + if ( str.size() && str[str.size()-1] == '\\' ) + { + parent.commandBuffer.push_back( str ); + return true; + } + else + { + std::list tmp_list; + tmp_list.push_back(str); + while (parent.commandBuffer.size() && parent.commandBuffer.back()[ + parent.commandBuffer.back().size()-1] == '\\') + { + tmp_list.push_back(parent.commandBuffer.back()); + parent.commandBuffer.pop_back(); + } + std::stringstream ss; + while (tmp_list.size()) + { + ss << tmp_list.back(); + tmp_list.pop_back(); + if (tmp_list.size()) + ss << "\n"; + } + parent.commandBuffer.push_back(ss.str()); + parent.stateStack.pop_back(); + return false; + } +} diff --git a/3rdparty/python-console/ParseHelper.cpp b/3rdparty/python-console/ParseHelper.cpp new file mode 100644 index 00000000..8944a84e --- /dev/null +++ b/3rdparty/python-console/ParseHelper.cpp @@ -0,0 +1,225 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "ParseHelper.h" +#include +#include +#include +#include +#include +#include +#include "ParseListener.h" + +#ifndef NDEBUG +void print(const ParseHelper::Indent& indent) +{ + std::string str = indent.Token; + for (int i = 0; i < str.size(); ++i) + { + switch (str.at(i)) + { + case ' ': + str[i] = 's'; + break; + case '\t': + str[i] = 't'; + break; + } + } + std::cout << str << "\n"; +} +#endif + +ParseHelper::Indent:: +Indent( ) +{ } + +ParseHelper::Indent:: +Indent( const std::string& indent ): + Token( indent ) +{ } + +ParseHelper::ParseState:: +ParseState( ParseHelper& parent_ ): parent( parent_ ) +{ } + +ParseHelper::ParseState:: +~ParseState( ) +{ } + +bool ParseHelper::PeekIndent( const std::string& str, Indent* indent ) +{ + if ( !str.size() || ! isspace(str[0]) ) + return false; + + int nonwhitespaceIndex = -1; + for (int i = 0; i < str.size(); ++i) + { + if (!isspace(str[i])) + { + nonwhitespaceIndex = i; + break; + } + } + if (nonwhitespaceIndex == -1) + { + return false; + } + std::string indentToken = str.substr(0, nonwhitespaceIndex); + indent->Token = indentToken; + return true; +} + +ParseHelper::ParseHelper( ) +{ } + +void ParseHelper::process( const std::string& str ) +{ +#ifndef NDEBUG + std::cout << "processing: (" << str << ")\n"; +#endif + + std::string top; + commandBuffer.push_back(str); + //std::string top = commandBuffer.back(); + //commandBuffer.pop_back(); + boost::shared_ptr blockStatePtr; + while (stateStack.size()) + { + top = commandBuffer.back(); + commandBuffer.pop_back(); + blockStatePtr = stateStack.back(); + if (blockStatePtr->process(top)) + return; + } + + if ( ! commandBuffer.size() ) + return; + + // standard state + top = commandBuffer.back(); + if ( !top.size() ) + { + reset( ); + broadcast( std::string() ); + return; + } +#ifndef NDEBUG + std::cout << "now processing: (" << top << ")\n"; +#endif + + { // check for unexpected indent + Indent ind; + bool isIndented = PeekIndent( top, &ind ); + if ( isIndented && + ! isInContinuation( ) ) + { + reset( ); + ParseMessage msg( 1, "IndentationError: unexpected indent"); + broadcast( msg ); + return; + } + } + + // enter indented block state + if ( top[top.size()-1] == ':' ) + { + boost::shared_ptr parseState( + new BlockParseState( *this ) ); + stateStack.push_back( parseState ); + return; + } + + if ( top[top.size()-1] == '\\' ) + { + boost::shared_ptr parseState( + new ContinuationParseState( *this ) ); + stateStack.push_back( parseState ); + return; + } + + if (BracketParseState::HasOpenBrackets( top )) + { + // FIXME: Every parse state should have its own local buffer + commandBuffer.pop_back( ); + boost::shared_ptr parseState( + new BracketParseState( *this, top ) ); + stateStack.push_back( parseState ); + return; + } + + // handle single-line statement + flush( ); +} + +bool ParseHelper::buffered( ) const +{ + return commandBuffer.size( ) || stateStack.size( ); +} + +void ParseHelper::flush( ) +{ + std::stringstream ss; + for (int i = 0; i < commandBuffer.size(); ++i ) + { + ss << commandBuffer[i] << "\n"; + } + commandBuffer.clear(); + + broadcast( ss.str() ); + // TODO: feed string to interpreter +} + +void ParseHelper::reset( ) +{ +// inContinuation = false; + stateStack.clear( ); + commandBuffer.clear( ); +} + +bool ParseHelper::isInContinuation( ) const +{ + return (stateStack.size() + && (boost::dynamic_pointer_cast( + stateStack.back()))); +} + +void ParseHelper::subscribe( ParseListener* listener ) +{ + listeners.push_back( listener ); +} + +void ParseHelper::unsubscribeAll( ) +{ + listeners.clear( ); +} + +void ParseHelper::broadcast( const ParseMessage& msg ) +{ + // broadcast signal + for (int i = 0; i < listeners.size(); ++i) + { + if (listeners[i]) + { + listeners[i]->parseEvent(msg); + } + } +} diff --git a/3rdparty/python-console/ParseHelper.h b/3rdparty/python-console/ParseHelper.h new file mode 100644 index 00000000..8824fc39 --- /dev/null +++ b/3rdparty/python-console/ParseHelper.h @@ -0,0 +1,169 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef PARSE_HELPER_H +#define PARSE_HELPER_H +#include +#include +#include +#include +#include "ParseMessage.h" + +class ParseListener; + +/** +Helps chunk lines of Python code into compilable statements. +*/ +class ParseHelper +{ +public: + struct Indent + { + std::string Token; + Indent( ); + Indent( const std::string& indent ); + }; + + /** + Handle different states of parsing. Subclasses override + process(const std::string&) to handle different types of multiline + statements. + */ + struct ParseState + { + ParseHelper& parent; + ParseState( ParseHelper& parent_ ); + virtual ~ParseState( ); + + /** + Processes a single line of user input. + + Subclasses should return false if further processing should be done. In + this case, the command buffer should be topped by the next statement to + parse. + + \return whether processing of the line is done. + */ + virtual bool process(const std::string& str) = 0; + }; + + struct ContinuationParseState : public ParseState + { + using ParseState::parent; + ContinuationParseState( ParseHelper& parent_ ); + virtual bool process( const std::string& str); + }; + + /** + Handle parsing a multiline indented block. Example of such a block: + + for i in range(10): + print i + print i*i + + */ + struct BlockParseState : public ParseState + { + Indent indent; + + BlockParseState( ParseHelper& parent ); + BlockParseState( ParseHelper& parent, const std::string& indent_ ); + + // return whether processing is finished + virtual bool process(const std::string& str); + + // return if there was an error + bool initializeIndent(const std::string& str); + }; + friend class BlockParseState; + + struct BracketParseState : public ParseState + { + static const std::string OpeningBrackets; + static const std::string ClosingBrackets; + + std::list brackets; + std::list m_buffer; + + /** + Return whether open brackets remain unclosed in \a str. + */ + static bool HasOpenBrackets(const std::string& str); + static bool LoadBrackets(const std::string& str, + std::list* stack); + + BracketParseState( ParseHelper& parent, const std::string& firstLine ); + + virtual bool process(const std::string& str); + }; + +protected: + // TODO: Create a ContinuationParseState to handle this + bool inContinuation; + std::vector< ParseListener* > listeners; + std::vector< boost::shared_ptr< ParseState > > stateStack; + std::vector< std::string > commandBuffer; + +public: + static bool PeekIndent( const std::string& str, Indent* indent ); + +public: + ParseHelper( ); + +public: + void process( const std::string& str ); + + bool buffered( ) const; + + /** + Generate a parse event from the current command buffer. + */ + void flush( ); + + /** + Reset the state of the helper. + */ + void reset( ); + + bool isInContinuation( ) const; + + void subscribe( ParseListener* listener ); + void unsubscribeAll( ); + void broadcast( const ParseMessage& msg ); + +}; // class ParseHelper + +inline bool operator== ( const ParseHelper::Indent& a, const ParseHelper::Indent& b ) +{ + return a.Token == b.Token; +} + +inline bool operator!= ( const ParseHelper::Indent& a, const ParseHelper::Indent& b ) +{ + return a.Token != b.Token; +} + +#ifndef NDEBUG +void print(const ParseHelper::Indent& indent); +#endif + +#endif // PARSE_HELPER_H diff --git a/3rdparty/python-console/ParseListener.cpp b/3rdparty/python-console/ParseListener.cpp new file mode 100644 index 00000000..36aaad22 --- /dev/null +++ b/3rdparty/python-console/ParseListener.cpp @@ -0,0 +1,29 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "ParseListener.h" +#include + +void EchoListener::parseEvent( const ParseMessage& msg ) +{ + std::cout << "echo(" << msg.errorCode << "): " << msg.message << "\n"; +} + diff --git a/3rdparty/python-console/ParseListener.h b/3rdparty/python-console/ParseListener.h new file mode 100644 index 00000000..28c501c4 --- /dev/null +++ b/3rdparty/python-console/ParseListener.h @@ -0,0 +1,42 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#ifndef PARSE_LISTENER_H +#define PARSE_LISTENER_H +#include "ParseMessage.h" + +/** +Interface to implement to receive parse events. +*/ +struct ParseListener +{ + virtual void parseEvent( const ParseMessage& msg ) = 0; +}; + +/** +Sample implementation of ParseListener that echoes messages. +*/ +struct EchoListener : public ParseListener +{ + virtual void parseEvent( const ParseMessage& msg ); +}; + +#endif // PARSE_LISTENER_H diff --git a/3rdparty/python-console/ParseMessage.cpp b/3rdparty/python-console/ParseMessage.cpp new file mode 100644 index 00000000..29fdbae0 --- /dev/null +++ b/3rdparty/python-console/ParseMessage.cpp @@ -0,0 +1,31 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "ParseMessage.h" + +ParseMessage::ParseMessage( const std::string& msg ): + errorCode( 0 ), message( msg ) +{ } + +ParseMessage::ParseMessage( int code, const std::string& msg ): + errorCode( code ), message( msg ) +{ } + diff --git a/3rdparty/python-console/ParseMessage.h b/3rdparty/python-console/ParseMessage.h new file mode 100644 index 00000000..9c77519e --- /dev/null +++ b/3rdparty/python-console/ParseMessage.h @@ -0,0 +1,38 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#ifndef PARSE_MESSAGE_H +#define PARSE_MESSAGE_H +#include + +/** +Basically a string + error code pair. Generated from ParseHelper so error-free +codeblocks can be sent to the Interpreter. +*/ +struct ParseMessage +{ + int errorCode; + std::string message; + + ParseMessage( const std::string& msg ); + ParseMessage( int code, const std::string& msg ); +}; +#endif //PARSE_MESSAGE_H diff --git a/3rdparty/python-console/README.md b/3rdparty/python-console/README.md new file mode 100644 index 00000000..a80bffe4 --- /dev/null +++ b/3rdparty/python-console/README.md @@ -0,0 +1,17 @@ +python-console +===== +This is the result of a small side project to write a Qt widget that +encapsulates an interactive Python shell. + +Quickstart +----- +You should have Qt4 and Python libraries. You will need CMake to build this +project as follows: + +1. mkdir build +2. cmake .. +3. make + +License +----- +This project is licensed under the [MIT](http://opensource.org/licenses/MIT) license. diff --git a/3rdparty/python-console/Utils.h b/3rdparty/python-console/Utils.h new file mode 100644 index 00000000..ebd4234f --- /dev/null +++ b/3rdparty/python-console/Utils.h @@ -0,0 +1,33 @@ +#ifndef PYTHON_CONSOLE_UTILS_H +#define PYTHON_CONSOLE_UTILS_H +#include +#include + +/** +InputIterator has value type of std::string. +*/ +template < class InputIterator > +std::string LongestCommonPrefix( InputIterator begin, InputIterator end ) +{ + if ( begin == end ) + return ""; + + const std::string& str0 = *begin; + if ( ! str0.size() ) + return ""; + + int endIndex = str0.size() - 1; + InputIterator it = begin; ++it; + for (; it != end; ++it) + { + const std::string& str = *it; + for (int j = 0; j <= endIndex; ++j) + { + if (j >= str.size() || str[j] != str0[j]) + endIndex = j - 1; + } + } + return (endIndex > 0)? str0.substr(0, endIndex + 1) : ""; +} + +#endif // PYTHON_CONSOLE_UTILS_H diff --git a/3rdparty/python-console/data/test.py b/3rdparty/python-console/data/test.py new file mode 100644 index 00000000..18390c0d --- /dev/null +++ b/3rdparty/python-console/data/test.py @@ -0,0 +1,2 @@ +for i in range(1, 10): + print i diff --git a/3rdparty/python-console/data/test2.py b/3rdparty/python-console/data/test2.py new file mode 100644 index 00000000..9e9725e7 --- /dev/null +++ b/3rdparty/python-console/data/test2.py @@ -0,0 +1,6 @@ +for i in range(1, 10): + print i + +for i in range(1, 10): + print i + print i diff --git a/3rdparty/python-console/data/test3.py b/3rdparty/python-console/data/test3.py new file mode 100644 index 00000000..19ad5eb5 --- /dev/null +++ b/3rdparty/python-console/data/test3.py @@ -0,0 +1,4 @@ +for i in range(1, 10): + print i +for i in range(1, 10): + print i diff --git a/3rdparty/python-console/data/test4.py b/3rdparty/python-console/data/test4.py new file mode 100644 index 00000000..11b64f63 --- /dev/null +++ b/3rdparty/python-console/data/test4.py @@ -0,0 +1,4 @@ +for i in range(2): + for j in range(3): + print j + print i diff --git a/3rdparty/python-console/data/test5.py b/3rdparty/python-console/data/test5.py new file mode 100644 index 00000000..1f80c998 --- /dev/null +++ b/3rdparty/python-console/data/test5.py @@ -0,0 +1,3 @@ +for i in range(2): + for j in range(3): + print i, j diff --git a/3rdparty/python-console/test_cli.cpp b/3rdparty/python-console/test_cli.cpp new file mode 100644 index 00000000..76d0f251 --- /dev/null +++ b/3rdparty/python-console/test_cli.cpp @@ -0,0 +1,63 @@ +#include +#include +#include "ParseHelper.h" +#include "ParseListener.h" +#include "Interpreter.h" + +const std::string STD_PROMPT = ">>> "; +const std::string MULTILINE_PROMPT = "... "; + +struct InterpreterRelay : public ParseListener +{ + Interpreter* m_interpreter; + + InterpreterRelay( ): + m_interpreter( new Interpreter ) + { } + + virtual void parseEvent( const ParseMessage& msg ) + { + if ( msg.errorCode ) + { + std::cout << "(" << msg.errorCode << ") " << msg.message << "\n"; + return; + } + else + { + int err; + std::string res = m_interpreter->interpret( msg.message, &err ); + std::cout << "(" << msg.errorCode << ") " << res << "\n"; + } + } +}; + +int main( int argc, char *argv[] ) +{ + Interpreter::Initialize( ); + const std::string* prompt = &STD_PROMPT; + ParseHelper helper; + ParseListener* listener = new InterpreterRelay; + helper.subscribe( listener ); + + std::string str; + std::cout << *prompt; + std::getline( std::cin, str ); + while ( str != "quit" ) + { + std::cout << str << "\n"; + helper.process( str ); + if ( helper.buffered( ) ) + { + prompt = &MULTILINE_PROMPT; + } + else + { + prompt = &STD_PROMPT; + } + std::cout << *prompt; + std::getline( std::cin, str ); + } + + Interpreter::Finalize( ); + return 0; +} diff --git a/3rdparty/python-console/test_console.cpp b/3rdparty/python-console/test_console.cpp new file mode 100644 index 00000000..d025f26b --- /dev/null +++ b/3rdparty/python-console/test_console.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include +#include + +#include "Console.h" +#include "Interpreter.h" + +Console* console; + +void SetupWindow( int argc, char *argv[] ) +{ + QMainWindow* window = new QMainWindow; + window->resize( 800, 600 ); + QWidget* centralWidget = new QWidget(window); + QGridLayout* layout = new QGridLayout(centralWidget); + console = new Console; + layout->addWidget(console, 0, 0, 1, 1); + window->setCentralWidget(centralWidget); + window->show( ); +} + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + Interpreter::Initialize( ); + + SetupWindow( argc, argv ); + + bool res = app.exec( ); + delete console; + Interpreter::Finalize( ); + return res; +} diff --git a/3rdparty/python-console/test_parse_helper.cpp b/3rdparty/python-console/test_parse_helper.cpp new file mode 100644 index 00000000..d530a5f1 --- /dev/null +++ b/3rdparty/python-console/test_parse_helper.cpp @@ -0,0 +1,51 @@ +/** +Copyright (c) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include +#include +#include "ParseHelper.h" +#include "ParseListener.h" + +/** +Demonstrates the ParseHelper. + +Call it with some of the python files in data/. +*/ +int main( int argc, char *argv[] ) +{ + if ( argc < 2 ) + { + std::cout << "Usage: " << argv[0] << " file\n"; + return 1; + } + ParseHelper helper; + helper.subscribe( new EchoListener ); + + std::ifstream ifs( argv[1] ); + while ( ! ifs.eof( ) ) + { + std::string str; + std::getline( ifs, str ); + helper.process( str ); + } + + return 0; +} diff --git a/3rdparty/python-console/test_python_interpreter.cpp b/3rdparty/python-console/test_python_interpreter.cpp new file mode 100644 index 00000000..69e16ed1 --- /dev/null +++ b/3rdparty/python-console/test_python_interpreter.cpp @@ -0,0 +1,22 @@ +#include +#include "Interpreter.h" + +int main( int argc, char *argv[] ) +{ + std::string commands[] = { + "from time import time,ctime\n", + "print 'Today is',ctime(time())\n" + }; + Interpreter::Initialize( ); + Interpreter* interpreter = new Interpreter; + for ( int i = 0; i < 2; ++i ) + { + int err; + std::string res = interpreter->interpret( commands[i], &err ); + std::cout << res; + } + delete interpreter; + + Interpreter::Finalize( ); + return 0; +}