Commit 68f3770c authored by Victor Yacovlev's avatar Victor Yacovlev

Initial Python support

parent b3b428dd
......@@ -27,3 +27,4 @@ scripts/*.pyo
/share/kumir2/widgets/secondarywindow/oxygen/.directory
/src/app/#CMakeLists.txt#
/share/kumir2/translations/qt_ru.ts
__pycache__
\ No newline at end of file
This diff is collapsed.
/home/victor/Projects/NIISI/kumir/python3_static_analyzer/analizer_instance.py
\ No newline at end of file
/home/victor/Projects/NIISI/kumir/python3_static_analyzer/kumir_constants.py
\ No newline at end of file
#!/usr/bin/env python3
import bdb
import io
import parser
import symbol
import sys
import pdb
import threading
import token
try:
import _kumir
debug = _kumir.debug
KUMIR_IMPORTED = True
except ImportError:
KUMIR_IMPORTED = False
import logging
logging.root.name = "run"
logging.root.setLevel(logging.DEBUG)
debug = logging.debug
class StdOut(io.TextIOBase):
def __init__(self):
super().__init__()
try:
import _kumir
self.write = _kumir.write_to_stdout
except ImportError:
self.write = sys.__stdout__.write
class StdErr(io.TextIOBase):
def __init__(self):
super().__init__()
try:
import _kumir
self.write = _kumir.write_to_stderr
except ImportError:
self.write = sys.__stderr__.write
if KUMIR_IMPORTED:
sys.stdout = StdOut()
sys.stderr = StdErr()
class Runner(bdb.Bdb):
def __init__(self):
super().__init__()
def user_return(self, frame, retval):
_kumir.user_return(frame, retval)
def user_call(self, frame, retval):
_kumir.user_call(frame, retval)
def user_line(self, frame):
if _kumir.user_line(frame):
self.set_step()
else:
self.set_quit()
def user_exception(self, frame, exc_info):
_kumir.user_exception(frame, exc_info)
def main_loop(self, filename, source):
self.reset()
self.set_step()
try:
self.run(source)
status = 0
except bdb.BdbQuit:
status = 1
except BaseException:
status = 2
return status
__runner = Runner()
def main_loop(filename, source):
return __runner.main_loop(filename, source)
def __extract_atoms_from_expression(items):
result = []
for item in items:
if isinstance(item, tuple) and item[0] == symbol.atom:
result += [(item[1][1], item[1][2])]
elif isinstance(item, tuple):
result += __extract_atoms_from_expression(item)
return result
def __extract_lvalue_from_statement(items, target):
left_parts = []
right_parts = []
parts = right_parts
for item in items:
if isinstance(item, tuple) and item[0] in [symbol.testlist_star_expr, symbol.exprlist]:
parts += [item[1:]]
elif isinstance(item, tuple) and item[0] == token.EQUAL:
left_parts = parts
right_parts = []
parts = right_parts
stmt_atoms = __extract_atoms_from_expression(left_parts)
for atom_name, atom_line in stmt_atoms:
line_atoms = target[atom_line - 1]
line_atoms += [atom_name]
def __extract_lvalue_from_for_statement(items, target):
for item in items:
if isinstance(item, tuple) and item[0] == symbol.exprlist:
for_assign_atoms = __extract_atoms_from_expression(item)
for atom_name, atom_line in for_assign_atoms:
line_atoms = target[atom_line - 1]
line_atoms += [atom_name]
elif isinstance(item, tuple) and item[0] == symbol.suite:
__extract_lvalue_atoms(item, target)
def __extract_lvalue_atoms(root, target):
if isinstance(root, tuple):
if root[0] == symbol.expr_stmt:
__extract_lvalue_from_statement(root[1:], target)
elif root[0] == symbol.for_stmt:
__extract_lvalue_from_for_statement(root[1:], target)
else:
for item in root:
if isinstance(item, tuple):
__extract_lvalue_atoms(item, target)
def extract_lvalue_atoms(source):
st = parser.suite(source)
lines_count = len(source.split("\n")) + 1
items = parser.st2tuple(st, line_info=True, col_info=True)
result = list()
for i in range(0, lines_count):
result += [list()]
__extract_lvalue_atoms(items, result)
return result
if __name__ == "__main__":
a = extract_lvalue_atoms("""
import sys
def main(arguments):
for index, argument in arguments:
a, b = index, argument
print(a)
print(b)
if __name__ == "__main__":
ret = main(sys.argv)
sys.exit(ret)
""")
print(a)
import io
import _kumir
class StdOut(io.TextIOBase):
def __init__(self):
super().__init__()
self.write = _kumir.write_output
class StdErr(io.TextIOBase):
def __init__(self):
super().__init__()
self.write = _kumir.write_error
class StdIn(io.TextIOBase):
def __init__(self):
super().__init__()
self.readline = _kumir.read_input
stdout = StdOut()
stderr = StdErr()
stdin = StdIn()
debug = _kumir.debug
import sys
import run_io
sys.stdout = run_io.stdout
sys.stderr = run_io.stderr
sys.stdin = run_io.stdin
run_io.debug("Kumir <--> Python wrapper initialized.");
set(QT_USE_QTMAIN 1)
find_package(Qt4 4.7.0 COMPONENTS QtCore QtGui REQUIRED)
include (${QT_USE_FILE})
set(CONFIGURATION_TEMPLATE
"Editor,Browser,Python3Language,!CoreGUI\(notabs,icon=python,nostartpage,nosessions\)"
)
set(SPLASHSCREEN
"coregui/splashscreens/python.png"
)
set(SRC ../main.cpp)
if(WIN32)
list(APPEND SRC kumir2-python.rc)
endif(WIN32)
#include(${QT_USE_FILE})
add_executable(kumir2-python WIN32 ${SRC})
target_link_libraries(kumir2-python ${QT_LIBRARIES} ExtensionSystem)
set_property(TARGET kumir2-python APPEND PROPERTY COMPILE_DEFINITIONS CONFIGURATION_TEMPLATE="${CONFIGURATION_TEMPLATE}")
set_property(TARGET kumir2-python APPEND PROPERTY COMPILE_DEFINITIONS SPLASHSCREEN="${SPLASHSCREEN}")
if (XCODE OR MSVC_IDE)
set_target_properties (kumir2-python PROPERTIES PREFIX "../")
endif(XCODE OR MSVC_IDE)
install(TARGETS kumir2-python DESTINATION ${EXEC_DIR})
IDI_ICON1 ICON DISCARDABLE "..\\..\\app_icons\\win32\\kumir2-python.ico"
project(Python3Language)
cmake_minimum_required(VERSION 2.8.3)
if(NOT DEFINED USE_QT)
set(USE_QT 4)
endif(NOT DEFINED USE_QT)
if(${USE_QT} GREATER 4)
# Find Qt5
find_package(Qt5 5.3.0 COMPONENTS Core Widgets REQUIRED)
include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} BEFORE)
set(QT_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Widgets_LIBRARIES})
else()
# Find Qt4
set(QT_USE_QTMAIN 1)
find_package(Qt4 4.7.0 COMPONENTS QtCore QtGui QtXml QtSvg REQUIRED)
include(${QT_USE_FILE})
endif()
include(../../kumir2_plugin.cmake)
find_package(PythonLibs 3.2)
include_directories(${PYTHON_INCLUDE_DIRS})
if(MSVC)
string(REGEX REPLACE
"libpython([0-9][0-9])\\.a"
"python\\1.lib"
PYTHON_LIBRARIES
"${PYTHON_LIBRARIES}"
)
endif()
add_definitions(-DQT_NO_KEYWORDS)
set(SOURCES
python3languageplugin.cpp
analizerinstance.cpp
pyutils.cpp
interpretercallback.cpp
pythonrunthread.cpp
actorshandler.cpp
)
set(MOC_HEADERS
python3languageplugin.h
analizerinstance.h
interpretercallback.h
pythonrunthread.h
actorshandler.h
)
if(${USE_QT} GREATER 4)
qt5_wrap_cpp(MOC_SOURCES ${MOC_HEADERS})
else()
qt4_wrap_cpp(MOC_SOURCES ${MOC_HEADERS})
endif()
copySpecFile(Python3Language)
add_library(Python3Language SHARED ${MOC_SOURCES} ${SOURCES})
handleTranslation(Python3Language)
target_link_libraries(Python3Language ${QT_LIBRARIES} ${PYTHON_LIBRARIES} DataFormats ExtensionSystem ${STDCXX_LIB} ${STDMATH_LIB})
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/share/kumir2/python3language)
copyResources(python3language)
install(TARGETS Python3Language DESTINATION ${PLUGINS_DIR})
name = Python3Language
provides= Analizer, Runner
#include "actorshandler.h"
#include "extensionsystem/pluginmanager.h"
namespace Python3Language {
ActorsHandler::ActorsHandler(QObject *parent)
: QObject(parent)
, syncSemaphore_(new QSemaphore(0))
{
initializeActors();
}
ActorsHandler* ActorsHandler::self = 0;
ActorsHandler* ActorsHandler::instance(QObject *parent)
{
if (!self) {
self = new ActorsHandler(parent);
}
return self;
}
void ActorsHandler::reset()
{
// Ensure semaphore value == 0
int sem = syncSemaphore_->available();
while (!sem) {
syncSemaphore_->release();
sem = syncSemaphore_->available();
}
while (sem) {
syncSemaphore_->acquire();
sem = syncSemaphore_->available();
}
// Reset all registered actors
Q_FOREACH(ActorInterface * actor, actors_) {
actor->reset();
}
}
void ActorsHandler::initializeActors()
{
using namespace ExtensionSystem;
QList<ActorInterface*> actors =
PluginManager::instance()->findPlugins<ActorInterface>();
names_.reserve(actors.size());
wrappers_.reserve(actors.size());
actors_.reserve(actors.size());
Q_FOREACH(ActorInterface * actor, actors) {
const QPair<QString,QString> names = generateActorNames(actor);
const QString & camelCaseName = names.first;
const QString & pythonicName = names.second;
names_.append(pythonicName);
actors_.append(actor);
wrappers_.append(createActorWrapper(wrappers_.size(), actor, camelCaseName));
actor->connectSync(this, SLOT(handleActorSync()));
}
}
QPair<QString,QString> ActorsHandler::generateActorNames(const ActorInterface *actor)
{
QString canonicalName = actorCanonicalName<QByteArray>(actor->asciiModuleName());
QString camelCaseName;
QString pythonicName;
bool nextIsUpper = true;
for (int i=0; i<canonicalName.length(); i++) {
const QChar source = canonicalName[i];
if (source.isSpace() || '@' == source) {
nextIsUpper = true;
pythonicName += "_";
}
else {
if (nextIsUpper)
camelCaseName += source.toUpper();
else
camelCaseName += source.toLower();
pythonicName += source.toLower();
nextIsUpper = false;
}
}
return QPair<QString,QString>(camelCaseName, pythonicName);
}
QString ActorsHandler::createActorWrapper(const int id, const ActorInterface *actor, const QString & camelCaseName)
{
QString r = "import _kumir\n\n\n";
r += "class " + camelCaseName + "Error(Exception):\n";
r += " def __init__(self, message):\n";
r += " self.message = message\n";
r += " def __str__(self):\n";
r += " return self.message\n";
r += "\n\n";
const ActorInterface::FunctionList functionList = actor->functionList();
Q_FOREACH(const ActorInterface::Function & func, functionList) {
QString funcName = func.asciiName.toLower();
funcName = funcName.replace(' ', '_');
funcName = funcName.replace('@', '_');
const ActorInterface::ArgumentList argList = func.arguments;
QStringList formalArgs;
QStringList passedArgs;
for (int i=0; i<argList.size(); i++) {
const ActorInterface::Argument & arg = argList.at(i);
if (ActorInterface::OutArgument!=arg.accessType) {
QString argName = arg.asciiName.toLower();
argName = argName.replace(' ', '_');
argName = argName.replace('@', '_');
if (argName.isEmpty()) {
argName = QString::fromLatin1("arg_%1").arg(i);
}
formalArgs.push_back(argName);
passedArgs.push_back(argName);
}
else {
passedArgs.push_back("0");
}
}
r += "def " + funcName + "(" + formalArgs.join(", ") + "):\n";
r += QString::fromLatin1(" __error__, __result__ = _kumir.actor_call(%1, %2, [%3])\n")
.arg(id).arg(func.id).arg(passedArgs.join(", "));
r += " if __error__:\n";
r += " raise " + camelCaseName + "Error(__error__)\n";
r += " else:\n";
r += " return __result__\n";
r += "\n";
}
return r;
}
QVariant ActorsHandler::call(int moduleId, int functionId, const QVariantList &arguments)
{
ActorInterface * actor = actors_[moduleId];
EvaluationStatus status = actor->evaluate(functionId, arguments);
QVariantList result;
result.append(QString()); // first item is an error text
result.append(QVariant()); // second item is a function result
switch (status) {
case ES_Error:
result[0] = actor->errorText();
break;
case ES_StackResult:
result[1] = actor->result();
break;
case ES_RezResult:
result[1] = actor->algOptResults();
break;
case ES_StackRezResult: {
QVariantList tuple = actor->algOptResults();
tuple.prepend(actor->result());
result[1] = tuple;
}
break;
case ES_Async:
syncSemaphore_->acquire();
result[0] = actor->errorText();
result[1] = actorCallResult_;
break;
default:
break;
}
return result;
}
void ActorsHandler::handleActorSync()
{
actorCallResult_.clear();
ActorInterface * actor = qobject_cast<ActorInterface*>(sender());
if (actor->algOptResults().size() > 0 && QVariant::Invalid != actor->result()) {
QVariantList tuple = actor->algOptResults();
tuple.prepend(actor->result());
actorCallResult_ = tuple;
}
else if (actor->algOptResults().size() > 0) {
actorCallResult_ = actor->algOptResults();
}
else {
actorCallResult_ = actor->result();
}
syncSemaphore_->release();
}
} // namespace Python3Language
#ifndef PYTHON3LANGUAGE_ACTORSHANDLER_H
#define PYTHON3LANGUAGE_ACTORSHANDLER_H
#include <QtCore>
#include "interfaces/actorinterface.h"
namespace Python3Language {
using namespace Shared;
class ActorsHandler : public QObject
{
Q_OBJECT
public:
static ActorsHandler* instance(QObject *parent = 0);
QVariant call(int moduleId, int functionId, const QVariantList & arguments);
void reset();
inline int size() const { return names_.size(); }
inline bool contains(const QString & name) const { return names_.contains(name); }
inline QString moduleWrapper(int moduleId) const { return wrappers_[moduleId]; }
inline QString moduleName(int moduleId) const { return names_[moduleId]; }
private Q_SLOTS:
void handleActorSync();
private /*methods*/:
explicit ActorsHandler(QObject *parent = 0);
void initializeActors();
static QPair<QString, QString> generateActorNames(const ActorInterface * actor);
static QString createActorWrapper(const int id,
const ActorInterface * actor,
const QString & camelCaseName
);
private /*fields*/:
static ActorsHandler* self;
QVector<QString> names_;
QVector<QString> wrappers_;
QVector<ActorInterface*> actors_;
QSemaphore* syncSemaphore_;
QVariant actorCallResult_;
};
} // namespace Python3Language
#endif // PYTHON3LANGUAGE_ACTORSHANDLER_H
#include "analizerinstance.h"
#include "python3languageplugin.h"
#include "pyutils.h"
namespace Python3Language {
PythonAnalizerInstance::PythonAnalizerInstance(Python3LanguagePlugin *parent,
const QString & extraPythonPath)
: QObject(parent)
, plugin_(parent)
, py_(0)
{
if (PyThreadState_GET())
::PyEval_AcquireLock();
else
::PyGILState_Ensure();
py_ = ::Py_NewInterpreter();
appendToSysPath(extraPythonPath);
initializePyAnalizer();
::PyEval_ReleaseThread(py_);
}
PythonAnalizerInstance::~PythonAnalizerInstance()
{
stopPythonInterpreter();
}
void PythonAnalizerInstance::stopPythonInterpreter()
{
if (py_) {
::PyEval_AcquireThread(py_);
::Py_EndInterpreter(py_);
::PyEval_ReleaseLock();
}
py_ = 0;
}
void PythonAnalizerInstance::initializePyAnalizer()
{
py_analizerInstance = ::PyImport_ImportModule("analizer_instance");
if (!py_analizerInstance) {
printPythonTraceback();
return;
}
py_setSourceDirName = ::PyObject_GetAttrString(