diff --git a/scripting/python_scripter.cc b/scripting/python_scripter.cc index e60a90c0..62a7409b 100644 --- a/scripting/python_scripter.cc +++ b/scripting/python_scripter.cc @@ -1,611 +1,615 @@ // Copyright (C) 2003 Dominique Devriese // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA // 02110-1301, USA. // mp: following a kind suggestion by David Faure the Python.h include has // been moved before all qt includes in order to avoid a clash related to // the "slots" identifier // krazy:excludeall=includes #undef _XOPEN_SOURCE // it will be defined inside Python #include #include "python_scripter.h" #include #include #include #include #include "../misc/common.h" #include "../misc/coordinate.h" #include "../misc/cubic-common.h" #include "../misc/kigtransform.h" #include "../objects/bogus_imp.h" #include "../objects/common.h" #include "../objects/circle_imp.h" #include "../objects/cubic_imp.h" #include "../objects/line_imp.h" #include "../objects/other_imp.h" #include "../objects/point_imp.h" #include "../objects/text_imp.h" #include "../objects/polygon_imp.h" using namespace boost::python; BOOST_PYTHON_MODULE_INIT( kig ) { class_( "Coordinate" ) .def( init() ) .def( init() ) .def( "invalidCoord", &Coordinate::invalidCoord ) .staticmethod( "invalidCoord" ) .def( "valid", &Coordinate::valid ) .def( "distance", &Coordinate::distance ) .def( "length", &Coordinate::length ) .def( "squareLength", &Coordinate::squareLength ) .def( "orthogonal", &Coordinate::orthogonal ) .def( "round", &Coordinate::round ) .def( "normalize", &Coordinate::normalize ) .def( -self ) // .def( self = self ) .def( self += self ) .def( self -= self ) .def( self *= other() ) .def( self *= other() ) .def( self /= other() ) .def( self / other() ) .def( self + self ) .def( self - self ) .def( self * other() ) .def( other() * self ) .def( self * self ) .def_readwrite( "x", &Coordinate::x ) .def_readwrite( "y", &Coordinate::y ) ; class_( "LineData" ) .def( init() ) .def( "dir", &LineData::dir ) .def( "length", &LineData::length ) .def( "isParallelTo", &LineData::isParallelTo ) .def_readwrite( "a", &LineData::a ) .def_readwrite( "b", &LineData::b ) ; // we need this cause Transformation::apply is overloaded and // otherwise using Transformation::apply would be ambiguous.. const Coordinate (Transformation::*transformapplyfunc)( const Coordinate& ) const = &Transformation::apply; class_( "Transformation", no_init ) .def( "apply", transformapplyfunc ) .def( "isHomothetic", &Transformation::isHomothetic ) .def( "inverse", &Transformation::inverse ) .def( "identity", &Transformation::identity ) .def( "translation", &Transformation::translation ) .def( "rotation", &Transformation::rotation ) .def( "pointReflection", &Transformation::pointReflection ) .def( "lineReflection", &Transformation::lineReflection ) .def( "castShadow", &Transformation::castShadow ) .def( "projectiveRotation", &Transformation::projectiveRotation ) .def( "scalingOverPoint", &Transformation::scalingOverPoint ) .def( "scalingOverLine", &Transformation::scalingOverLine ) .def( self * self ) .def( self == self ) .staticmethod( "identity" ) .staticmethod( "translation" ) .staticmethod( "rotation" ) .staticmethod( "pointReflection" ) .staticmethod( "lineReflection" ) .staticmethod( "castShadow" ) .staticmethod( "projectiveRotation" ) .staticmethod( "scalingOverPoint" ) .staticmethod( "scalingOverLine" ) ; class_( "ObjectType", no_init ) .def( "fromInternalName", &ObjectImpType::typeFromInternalName, return_value_policy() ) .staticmethod( "fromInternalName" ) .def( "inherits", &ObjectImpType::inherits ) .def( "internalName", &ObjectImpType::internalName ) .def( "translatedName", &ObjectImpType::translatedName ) .def( "selectStatement", &ObjectImpType::selectStatement ) .def( "removeAStatement", &ObjectImpType::removeAStatement ) .def( "addAStatement", &ObjectImpType::addAStatement ) .def( "moveAStatement", &ObjectImpType::moveAStatement ) .def( "attachToThisStatement", &ObjectImpType::attachToThisStatement ) ; class_( "Object", no_init ) .def( "stype", &ObjectImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "inherits", &ObjectImp::inherits ) .def( "transform", &ObjectImp::transform, return_value_policy() ) .def( "valid", &ObjectImp::valid ) .def( "copy", &ObjectImp::copy, return_value_policy() ) .def( "equals", &ObjectImp::equals ) ; class_, boost::noncopyable>( "Curve", no_init ) .def( "stype", &CurveImp::stype, return_value_policy() ) .staticmethod( "stype" ) // .def( "getParam", &CurveImp::getParam ) // .def( "getPoint", &CurveImp::getPoint ); ; class_ >( "Point", init() ) .def( "stype", &PointImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "coordinate", &PointImp::coordinate, return_internal_reference<1>() ) .def( "setCoordinate", &PointImp::setCoordinate ) ; class_, boost::noncopyable >( "AbstractLine", no_init ) .def( "stype", &AbstractLineImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "slope", &AbstractLineImp::slope ) .def( "equationString", &AbstractLineImp::equationString ) .def( "data", &AbstractLineImp::data ) ; class_ >( "Segment", init() ) .def( "stype", &SegmentImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( init() ) .def( "length", &SegmentImp::length ) ; class_ >( "Ray", init() ) .def( "stype", &RayImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( init() ) ; class_ >( "Line", init() ) .def( "stype", &LineImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( init() ) ; class_( "ConicCartesianData", init() ) .def( init() ) .def( "invalidData", &ConicCartesianData::invalidData ) .staticmethod( "invalidData" ) .def( "valid", &ConicCartesianData::valid ) // .def( init() ) // .def_readwrite( "coeffs", &ConicCartesianData::coeffs ) ; class_( "ConicPolarData", init() ) .def( init() ) .def_readwrite( "focus1", &ConicPolarData::focus1 ) .def_readwrite( "pdimen", &ConicPolarData::pdimen ) .def_readwrite( "ecostheta0", &ConicPolarData::ecostheta0 ) .def_readwrite( "esintheta0", &ConicPolarData::esintheta0 ) ; class_, boost::noncopyable >( "Conic", no_init ) .def( "stype", &ConicImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "conicType", &ConicImp::conicType ) // .def( "conicTypeString", &ConicImp::conicTypeString ) // .def( "cartesianEquationString", &ConicImp::cartesianEquationString ) // .def( "polarEquationString", &ConicImp::polarEquationString ) .def( "cartesianData", &ConicImp::cartesianData ) .def( "polarData", &ConicImp::polarData ) .def( "focus1", &ConicImp::focus1 ) .def( "focus2", &ConicImp::focus2 ) ; class_ >( "CartesianConic", init() ) ; class_ >( "PolarConic", init() ) ; class_ >( "Circle", init() ) .def( "stype", &CircleImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "center", &CircleImp::center ) .def( "radius", &CircleImp::radius ) .def( "squareRadius", &CircleImp::squareRadius ) .def( "surface", &CircleImp::surface ) .def( "circumference", &CircleImp::circumference ) ; class_, boost::noncopyable>( "Polygon", no_init ) .def( "stype", &FilledPolygonImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "npoints", &FilledPolygonImp::npoints ) .def( "perimeter", &FilledPolygonImp::cperimeter ) .def( "area", &FilledPolygonImp::area ) .def( "windingNumber", &FilledPolygonImp::windingNumber ) ; class_ >( "Vector", init() ) .def( "stype", &VectorImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "length", &VectorImp::length ) .def( "dir", &VectorImp::dir ) .def( "data", &VectorImp::data ) ; //TODO find how to mark default class_ >( "Angle", init() ) .def( "stype", &AngleImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "size", &AngleImp::size ) .def( "point", &AngleImp::point ) .def( "startAngle", &AngleImp::startAngle ) .def( "angle", &AngleImp::angle ) ; class_ >( "Arc", init() ) .def( "stype", &ArcImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "startAngle", &ArcImp::startAngle ) .def( "angle", &ArcImp::angle ) .def( "radius", &ArcImp::radius ) .def( "center", &ArcImp::center ) .def( "firstEndPoint", &ArcImp::firstEndPoint ) .def( "secondEndPoint", &ArcImp::secondEndPoint ) .def( "sectorSurface", &ArcImp::sectorSurface ) ; class_, boost::noncopyable >( "BogusObject", no_init ) .def( "stype", &BogusImp::stype, return_value_policy() ) .staticmethod( "stype" ) ; class_ >( "InvalidObject", init<>() ) .def( "stype", &InvalidImp::stype, return_value_policy() ) .staticmethod( "stype" ) ; class_ >( "DoubleObject", init() ) .def( "stype", &DoubleImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "data", &DoubleImp::data ) .def( "setData", &DoubleImp::setData ) ; class_ >( "IntObject", init() ) .def( "stype", &IntImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "data", &IntImp::data ) .def( "setData", &IntImp::setData ) ; class_ >( "StringObject", init() ) .def( "stype", &StringImp::stype, return_value_policy() ) .staticmethod( "stype" ) // .def( "data", &StringImp::data ) // .def( "setData", &StringImp::setData ) ; class_ >( "TestResultObject", no_init ) .def( "stype", &TestResultImp::stype, return_value_policy() ) .staticmethod( "stype" ) // .def( "data", &TestResultImp::data ) ; // class_ >( "Text", init() ) // .def( "stype", &TextImp::stype, // return_value_policy() ) // .staticmethod( "stype" ) // .def( "text", &TextImp::text ) // .def( "coordinate", &TextImp::coordinate ) // .def( "hasFrame", &TextImp::hasFrame ) // ; class_ >( "NumericObject", no_init ) .def( "stype", &NumericTextImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "value", &NumericTextImp::getValue ) ; class_ >( "BooleanObject", no_init ) .def( "stype", &BoolTextImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "value", &BoolTextImp::getValue ) ; class_( "CubicCartesianData", init() ) .def( "invalidData", &CubicCartesianData::invalidData ) .staticmethod( "invalidData" ) .def( "valid", &CubicCartesianData::valid ) // .def( init() ) // .def_readwrite( "coeffs", &CubicCartesianData::coeffs ) ; class_ >( "Cubic", init() ) .def( "stype", &CubicImp::stype, return_value_policy() ) .staticmethod( "stype" ) .def( "data", &CubicImp::data ) ; } PythonScripter* PythonScripter::instance() { static PythonScripter t; return &t; } class PythonScripter::Private { public: dict mainnamespace; }; // allocates a new string using new [], and copies contents into it.. static char* newstring( const char* contents ) { char* ret = new char[strlen( contents ) + 1]; strcpy( ret, contents ); return ret; } PythonScripter::PythonScripter() { d = new Private; // tell the python interpreter about our API.. // the newstring stuff is to prevent warnings about conversion from // const char* to char*.. char* s = newstring( "kig" ); +#if PY_VERSION_HEX >= 0x03000000 + PyImport_AppendInittab( s, PyInit_kig ); +#else PyImport_AppendInittab( s, initkig ); +#endif // we can't delete this yet, since python keeps a pointer to it.. // This means we have a small but harmless memory leak here, but it // doesn't hurt at all, since it could only be freed at the end of // the program, at which time it is freed by the system anyway if we // don't do it.. //delete [] s; Py_Initialize(); s = newstring( "import math; from math import *;" ); PyRun_SimpleString( s ); delete [] s; s = newstring( "import kig; from kig import *;" ); PyRun_SimpleString( s ); delete [] s; s = newstring( "import traceback;" ); PyRun_SimpleString( s ); delete [] s; // find the main namespace.. s = newstring( "__main__" ); handle<> main_module( borrowed( PyImport_AddModule( s ) ) ); delete [] s; handle<> mnh(borrowed( PyModule_GetDict(main_module.get()) )); d->mainnamespace = extract( mnh.get() ); } PythonScripter::~PythonScripter() { PyErr_Clear(); Py_Finalize(); delete d; } class CompiledPythonScript::Private { public: int ref; object calcfunc; // TODO // object movefunc; }; ObjectImp* CompiledPythonScript::calc( const Args& args, const KigDocument& ) { return PythonScripter::instance()->calc( *this, args ); } CompiledPythonScript::~CompiledPythonScript() { --d->ref; if ( d->ref == 0 ) delete d; } CompiledPythonScript::CompiledPythonScript( Private* ind ) : d( ind ) { ++d->ref; } CompiledPythonScript PythonScripter::compile( const char* code ) { clearErrors(); dict retdict; bool error = false; try { (void) PyRun_String( const_cast( code ), Py_file_input, d->mainnamespace.ptr(), retdict.ptr() ); } catch( ... ) { error = true; }; error |= static_cast( PyErr_Occurred() ); if ( error ) { saveErrors(); retdict.clear(); } // debugging stuff, removed. // std::string dictstring = extract( str( retdict ) ); CompiledPythonScript::Private* ret = new CompiledPythonScript::Private; ret->ref = 0; ret->calcfunc = retdict.get( "calc" ); return CompiledPythonScript( ret ); } CompiledPythonScript::CompiledPythonScript( const CompiledPythonScript& s ) : d( s.d ) { ++d->ref; } std::string PythonScripter::lastErrorExceptionType() const { return lastexceptiontype; } std::string PythonScripter::lastErrorExceptionValue() const { return lastexceptionvalue; } std::string PythonScripter::lastErrorExceptionTraceback() const { return lastexceptiontraceback; } ObjectImp* PythonScripter::calc( CompiledPythonScript& script, const Args& args ) { clearErrors(); object calcfunc = script.d->calcfunc; try { std::vector objectvect; objectvect.reserve( args.size() ); for ( int i = 0; i < (int) args.size(); ++i ) { object o( boost::ref( *args[i] ) ); objectvect.push_back( o ); } handle<> argstuph( PyTuple_New( args.size() ) ); for ( int i = 0; i < (int) objectvect.size(); ++i ) { PyTuple_SetItem( argstuph.get(), i, (objectvect.begin() +i)->ptr() ); }; tuple argstup( argstuph ); handle<> reth( PyEval_CallObject( calcfunc.ptr(), argstup.ptr() ) ); // object resulto = calcfunc( argstup ); // handle<> reth( PyEval_CallObject( calcfunc.ptr(), args ) ); object resulto( reth ); extract result( resulto ); if( ! result.check() ) return new InvalidImp; else { ObjectImp& ret = result(); return ret.copy(); }; } catch( ... ) { saveErrors(); return new InvalidImp; }; } void PythonScripter::saveErrors() { erroroccurred = true; PyObject* poexctype; PyObject* poexcvalue; PyObject* poexctraceback; PyErr_Fetch( &poexctype, &poexcvalue, &poexctraceback ); handle<> exctypeh( poexctype ); handle<> excvalueh( poexcvalue ); object exctype( exctypeh ); object excvalue( excvalueh ); object exctraceback; if ( poexctraceback ) { handle<> exctracebackh( poexctraceback ); exctraceback = object( exctracebackh ); } lastexceptiontype = extract( str( exctype ) )(); lastexceptionvalue = extract( str( excvalue ) )(); object printexcfunc = d->mainnamespace[ "traceback" ].attr( "format_exception" ); list tracebacklist = extract( printexcfunc( exctype, excvalue, exctraceback ) )(); str tracebackstr( "" ); while ( true ) { try { str s = extract( tracebacklist.pop() ); tracebackstr += s; } catch( ... ) { break; } } lastexceptiontraceback = extract( tracebackstr )(); PyErr_Clear(); } void PythonScripter::clearErrors() { PyErr_Clear(); lastexceptiontype.clear(); lastexceptionvalue.clear(); lastexceptiontraceback.clear(); erroroccurred = false; } bool CompiledPythonScript::valid() { return !!d->calcfunc; } bool PythonScripter::errorOccurred() const { return erroroccurred; }