1 // SPDX-FileCopyrightText: 2003 Dominique Devriese <devriese@kde.org>
2 
3 // SPDX-License-Identifier: GPL-2.0-or-later
4 
5 // mp: following a kind suggestion by David Faure the Python.h include has
6 // been moved before all qt includes in order to avoid a clash related to
7 // the "slots" identifier
8 // krazy:excludeall=includes
9 #undef _XOPEN_SOURCE // it will be defined inside Python
10 #include <Python.h>
11 #include "python_scripter.h"
12 
13 #include <iostream>
14 #include <string>
15 
16 #include <boost/python.hpp>
17 #include <boost/mpl/bool.hpp>
18 
19 #include "../misc/common.h"
20 #include "../misc/coordinate.h"
21 #include "../misc/cubic-common.h"
22 #include "../misc/kigtransform.h"
23 #include "../objects/bogus_imp.h"
24 #include "../objects/common.h"
25 #include "../objects/circle_imp.h"
26 #include "../objects/cubic_imp.h"
27 #include "../objects/line_imp.h"
28 #include "../objects/other_imp.h"
29 #include "../objects/point_imp.h"
30 #include "../objects/text_imp.h"
31 #include "../objects/polygon_imp.h"
32 
33 using namespace boost::python;
34 
BOOST_PYTHON_MODULE_INIT(kig)35 BOOST_PYTHON_MODULE_INIT( kig )
36 {
37   class_<Coordinate>( "Coordinate" )
38     .def( init<double, double>() )
39     .def( init<const Coordinate&>() )
40     .def( "invalidCoord", &Coordinate::invalidCoord )
41     .staticmethod( "invalidCoord" )
42     .def( "valid", &Coordinate::valid )
43     .def( "distance", &Coordinate::distance )
44     .def( "length", &Coordinate::length )
45     .def( "squareLength", &Coordinate::squareLength )
46     .def( "orthogonal", &Coordinate::orthogonal )
47     .def( "round", &Coordinate::round )
48     .def( "normalize", &Coordinate::normalize )
49     .def( -self )
50 //    .def( self = self )
51     .def( self += self )
52     .def( self -= self )
53     .def( self *= other<double>() )
54     .def( self *= other<int>() )
55     .def( self /= other<double>() )
56     .def( self / other<double>() )
57     .def( self + self )
58     .def( self - self )
59     .def( self * other<double>() )
60     .def( other<double>() * self )
61     .def( self * self )
62     .def_readwrite( "x", &Coordinate::x )
63     .def_readwrite( "y", &Coordinate::y )
64     ;
65 
66   class_<LineData>( "LineData" )
67     .def( init<Coordinate, Coordinate>() )
68     .def( "dir", &LineData::dir )
69     .def( "length", &LineData::length )
70     .def( "isParallelTo", &LineData::isParallelTo )
71     .def_readwrite( "a", &LineData::a )
72     .def_readwrite( "b", &LineData::b )
73     ;
74 
75   // we need this cause Transformation::apply is overloaded and
76   // otherwise using Transformation::apply would be ambiguous..
77   const Coordinate (Transformation::*transformapplyfunc)( const Coordinate& ) const = &Transformation::apply;
78   class_<Transformation>( "Transformation", no_init )
79     .def( "apply", transformapplyfunc )
80     .def( "isHomothetic", &Transformation::isHomothetic )
81     .def( "inverse", &Transformation::inverse )
82     .def( "identity", &Transformation::identity )
83     .def( "translation", &Transformation::translation )
84     .def( "rotation", &Transformation::rotation )
85     .def( "pointReflection", &Transformation::pointReflection )
86     .def( "lineReflection", &Transformation::lineReflection )
87     .def( "castShadow", &Transformation::castShadow )
88     .def( "projectiveRotation", &Transformation::projectiveRotation )
89     .def( "scalingOverPoint", &Transformation::scalingOverPoint )
90     .def( "scalingOverLine", &Transformation::scalingOverLine )
91     .def( self * self )
92     .def( self == self )
93     .staticmethod( "identity" )
94     .staticmethod( "translation" )
95     .staticmethod( "rotation" )
96     .staticmethod( "pointReflection" )
97     .staticmethod( "lineReflection" )
98     .staticmethod( "castShadow" )
99     .staticmethod( "projectiveRotation" )
100     .staticmethod( "scalingOverPoint" )
101     .staticmethod( "scalingOverLine" )
102     ;
103 
104   class_<ObjectImpType, boost::noncopyable>( "ObjectType", no_init )
105     .def( "fromInternalName", &ObjectImpType::typeFromInternalName,
106           return_value_policy<reference_existing_object>() )
107     .staticmethod( "fromInternalName" )
108     .def( "inherits", &ObjectImpType::inherits )
109     .def( "internalName", &ObjectImpType::internalName )
110     .def( "translatedName", &ObjectImpType::translatedName )
111     .def( "selectStatement", &ObjectImpType::selectStatement )
112     .def( "removeAStatement", &ObjectImpType::removeAStatement )
113     .def( "addAStatement", &ObjectImpType::addAStatement )
114     .def( "moveAStatement", &ObjectImpType::moveAStatement )
115     .def( "attachToThisStatement", &ObjectImpType::attachToThisStatement )
116     ;
117 
118   class_<ObjectImp, boost::noncopyable>( "Object", no_init )
119     .def( "stype", &ObjectImp::stype,
120           return_value_policy<reference_existing_object>() )
121     .staticmethod( "stype" )
122     .def( "inherits", &ObjectImp::inherits )
123     .def( "transform", &ObjectImp::transform,
124           return_value_policy<manage_new_object>() )
125     .def( "valid", &ObjectImp::valid )
126     .def( "copy", &ObjectImp::copy,
127           return_value_policy<manage_new_object>() )
128     .def( "equals", &ObjectImp::equals )
129     ;
130 
131   class_<CurveImp, bases<ObjectImp>, boost::noncopyable>( "Curve", no_init )
132     .def( "stype", &CurveImp::stype,
133           return_value_policy<reference_existing_object>() )
134     .staticmethod( "stype" )
135 //    .def( "getParam", &CurveImp::getParam )
136 //    .def( "getPoint", &CurveImp::getPoint );
137     ;
138   class_<PointImp, bases<ObjectImp> >( "Point", init<Coordinate>() )
139     .def( "stype", &PointImp::stype,
140           return_value_policy<reference_existing_object>() )
141     .staticmethod( "stype" )
142     .def( "coordinate", &PointImp::coordinate,
143           return_internal_reference<1>() )
144     .def( "setCoordinate", &PointImp::setCoordinate )
145     ;
146 
147   class_<AbstractLineImp, bases<CurveImp>, boost::noncopyable >( "AbstractLine", no_init )
148     .def( "stype", &AbstractLineImp::stype,
149           return_value_policy<reference_existing_object>() )
150     .staticmethod( "stype" )
151     .def( "slope", &AbstractLineImp::slope )
152     .def( "equationString", &AbstractLineImp::equationString )
153     .def( "data", &AbstractLineImp::data )
154     ;
155 
156   class_<SegmentImp, bases<AbstractLineImp> >( "Segment", init<Coordinate, Coordinate>() )
157     .def( "stype", &SegmentImp::stype,
158           return_value_policy<reference_existing_object>() )
159     .staticmethod( "stype" )
160     .def( init<LineData>() )
161     .def( "length", &SegmentImp::length )
162     ;
163 
164   class_<RayImp, bases<AbstractLineImp> >( "Ray", init<Coordinate, Coordinate>() )
165     .def( "stype", &RayImp::stype,
166           return_value_policy<reference_existing_object>() )
167     .staticmethod( "stype" )
168     .def( init<LineData>() )
169     ;
170 
171   class_<LineImp, bases<AbstractLineImp> >( "Line", init<Coordinate, Coordinate>() )
172     .def( "stype", &LineImp::stype,
173           return_value_policy<reference_existing_object>() )
174     .staticmethod( "stype" )
175     .def( init<LineData>() )
176     ;
177 
178   class_<ConicCartesianData>( "ConicCartesianData", init<double,double,double,double,double,double>() )
179     .def( init<ConicPolarData>() )
180     .def( "invalidData", &ConicCartesianData::invalidData )
181     .staticmethod( "invalidData" )
182     .def( "valid", &ConicCartesianData::valid )
183 //    .def( init<double[6]>() )
184 //    .def_readwrite( "coeffs", &ConicCartesianData::coeffs )
185     ;
186 
187   class_<ConicPolarData>( "ConicPolarData", init<Coordinate, double, double, double>() )
188     .def( init<ConicCartesianData>() )
189     .def_readwrite( "focus1", &ConicPolarData::focus1 )
190     .def_readwrite( "pdimen", &ConicPolarData::pdimen )
191     .def_readwrite( "ecostheta0", &ConicPolarData::ecostheta0 )
192     .def_readwrite( "esintheta0", &ConicPolarData::esintheta0 )
193     ;
194 
195   class_<ConicImp, bases<CurveImp>, boost::noncopyable >( "Conic", no_init )
196     .def( "stype", &ConicImp::stype,
197           return_value_policy<reference_existing_object>() )
198     .staticmethod( "stype" )
199     .def( "conicType", &ConicImp::conicType )
200 //    .def( "conicTypeString", &ConicImp::conicTypeString )
201 //    .def( "cartesianEquationString", &ConicImp::cartesianEquationString )
202 //    .def( "polarEquationString", &ConicImp::polarEquationString )
203     .def( "cartesianData", &ConicImp::cartesianData )
204     .def( "polarData", &ConicImp::polarData )
205     .def( "focus1", &ConicImp::focus1 )
206     .def( "focus2", &ConicImp::focus2 )
207     ;
208 
209   class_<ConicImpCart, bases<ConicImp> >( "CartesianConic", init<ConicCartesianData>() )
210     ;
211   class_<ConicImpPolar, bases<ConicImp> >( "PolarConic", init<ConicPolarData>() )
212     ;
213 
214   class_<CircleImp, bases<ConicImp> >( "Circle", init<Coordinate, double>() )
215     .def( "stype", &CircleImp::stype,
216           return_value_policy<reference_existing_object>() )
217     .staticmethod( "stype" )
218     .def( "center", &CircleImp::center )
219     .def( "radius", &CircleImp::radius )
220     .def( "squareRadius", &CircleImp::squareRadius )
221     .def( "surface", &CircleImp::surface )
222     .def( "circumference", &CircleImp::circumference )
223     ;
224 
225   class_<FilledPolygonImp, bases<ObjectImp>, boost::noncopyable>( "Polygon", no_init )
226     .def( "stype", &FilledPolygonImp::stype,
227           return_value_policy<reference_existing_object>() )
228     .staticmethod( "stype" )
229     .def( "npoints", &FilledPolygonImp::npoints )
230     .def( "perimeter", &FilledPolygonImp::cperimeter )
231     .def( "area", &FilledPolygonImp::area )
232     .def( "windingNumber", &FilledPolygonImp::windingNumber )
233     ;
234 
235   class_<VectorImp, bases<CurveImp> >( "Vector", init<Coordinate, Coordinate>() )
236     .def( "stype", &VectorImp::stype,
237           return_value_policy<reference_existing_object>() )
238     .staticmethod( "stype" )
239     .def( "length", &VectorImp::length )
240     .def( "dir", &VectorImp::dir )
241     .def( "data", &VectorImp::data )
242     ;
243 
244   //TODO find how to mark default
245   class_<AngleImp, bases<ObjectImp> >( "Angle", init<Coordinate, double, double, bool>() )
246     .def( "stype", &AngleImp::stype,
247           return_value_policy<reference_existing_object>() )
248     .staticmethod( "stype" )
249     .def( "size", &AngleImp::size )
250     .def( "point", &AngleImp::point )
251     .def( "startAngle", &AngleImp::startAngle )
252     .def( "angle", &AngleImp::angle )
253     ;
254 
255   class_<ArcImp, bases<ObjectImp> >( "Arc", init<Coordinate, double, double, double>() )
256     .def( "stype", &ArcImp::stype,
257           return_value_policy<reference_existing_object>() )
258     .staticmethod( "stype" )
259     .def( "startAngle", &ArcImp::startAngle )
260     .def( "angle", &ArcImp::angle )
261     .def( "radius", &ArcImp::radius )
262     .def( "center", &ArcImp::center )
263     .def( "firstEndPoint", &ArcImp::firstEndPoint )
264     .def( "secondEndPoint", &ArcImp::secondEndPoint )
265     .def( "sectorSurface", &ArcImp::sectorSurface )
266     ;
267 
268   class_<BogusImp, bases<ObjectImp>, boost::noncopyable >( "BogusObject", no_init )
269     .def( "stype", &BogusImp::stype,
270           return_value_policy<reference_existing_object>() )
271     .staticmethod( "stype" )
272     ;
273 
274   class_<InvalidImp, bases<BogusImp> >( "InvalidObject", init<>() )
275     .def( "stype", &InvalidImp::stype,
276           return_value_policy<reference_existing_object>() )
277     .staticmethod( "stype" )
278     ;
279 
280   class_<DoubleImp, bases<BogusImp> >( "DoubleObject", init<double>() )
281     .def( "stype", &DoubleImp::stype,
282           return_value_policy<reference_existing_object>() )
283     .staticmethod( "stype" )
284     .def( "data", &DoubleImp::data )
285     .def( "setData", &DoubleImp::setData )
286     ;
287 
288   class_<IntImp, bases<BogusImp> >( "IntObject", init<int>() )
289     .def( "stype", &IntImp::stype,
290           return_value_policy<reference_existing_object>() )
291     .staticmethod( "stype" )
292     .def( "data", &IntImp::data )
293     .def( "setData", &IntImp::setData )
294     ;
295 
296   class_<StringImp, bases<BogusImp> >( "StringObject", init<char*>() )
297     .def( "stype", &StringImp::stype,
298           return_value_policy<reference_existing_object>() )
299     .staticmethod( "stype" )
300 //     .def( "data", &StringImp::data )
301 //     .def( "setData", &StringImp::setData )
302     ;
303 
304   class_<TestResultImp, bases<BogusImp> >( "TestResultObject", no_init )
305     .def( "stype", &TestResultImp::stype,
306           return_value_policy<reference_existing_object>() )
307     .staticmethod( "stype" )
308 //     .def( "data", &TestResultImp::data )
309     ;
310 
311 //  class_<TextImp, bases<ObjectImp> >( "Text", init<string, Coordinate, bool>() )
312 //    .def( "stype", &TextImp::stype,
313 //          return_value_policy<reference_existing_object>() )
314 //    .staticmethod( "stype" )
315 //    .def( "text", &TextImp::text )
316 //    .def( "coordinate", &TextImp::coordinate )
317 //    .def( "hasFrame", &TextImp::hasFrame )
318 //    ;
319 
320   class_<NumericTextImp, bases<ObjectImp> >( "NumericObject", no_init )
321     .def( "stype", &NumericTextImp::stype,
322           return_value_policy<reference_existing_object>() )
323     .staticmethod( "stype" )
324     .def( "value", &NumericTextImp::getValue )
325     ;
326 
327   class_<BoolTextImp, bases<ObjectImp> >( "BooleanObject", no_init )
328     .def( "stype", &BoolTextImp::stype,
329           return_value_policy<reference_existing_object>() )
330     .staticmethod( "stype" )
331     .def( "value", &BoolTextImp::getValue )
332     ;
333 
334   class_<CubicCartesianData>( "CubicCartesianData", init<double,double,double,double,double,double,double,double,double,double>() )
335     .def( "invalidData", &CubicCartesianData::invalidData )
336     .staticmethod( "invalidData" )
337     .def( "valid", &CubicCartesianData::valid )
338 //    .def( init<double[10]>() )
339 //    .def_readwrite( "coeffs", &CubicCartesianData::coeffs )
340     ;
341 
342    class_<CubicImp, bases<CurveImp> >( "Cubic", init<CubicCartesianData>() )
343      .def( "stype", &CubicImp::stype,
344            return_value_policy<reference_existing_object>() )
345      .staticmethod( "stype" )
346      .def( "data", &CubicImp::data )
347      ;
348 
349 }
350 
351 // helper class to initialize Python in a constructor;
352 // this way, PythonScripter::Private inherits from it and thus
353 // already has Python initialized before the class members
354 // (like 'mainnamespace') are initialized
355 class PythonInitializer
356 {
357 public:
358   PythonInitializer();
359 };
360 
PythonInitializer()361 PythonInitializer::PythonInitializer()
362 {
363   // tell the python interpreter about our API..
364 
365   PyImport_AppendInittab( "kig", PyInit_kig );
366 
367   Py_Initialize();
368 
369   PyRun_SimpleString( "import math; from math import *;" );
370   PyRun_SimpleString( "import kig; from kig import *;" );
371   PyRun_SimpleString( "import traceback;" );
372 }
373 
instance()374 PythonScripter* PythonScripter::instance()
375 {
376   static PythonScripter t;
377   return &t;
378 }
379 
380 class PythonScripter::Private : private PythonInitializer
381 {
382 public:
383   dict mainnamespace;
384 };
385 
PythonScripter()386 PythonScripter::PythonScripter()
387 {
388   d = new Private;
389 
390   // find the main namespace..
391 
392   handle<> main_module( borrowed( PyImport_AddModule( "__main__" ) ) );
393 
394   handle<> mnh(borrowed( PyModule_GetDict(main_module.get()) ));
395   d->mainnamespace = extract<dict>( mnh.get() );
396 }
397 
~PythonScripter()398 PythonScripter::~PythonScripter()
399 {
400   PyErr_Clear();
401   delete d;
402   // Py_FinalizeEx();
403   Py_Finalize();  // maintained for compatibility reasons with python2
404 }
405 
406 class CompiledPythonScript::Private
407 {
408 public:
409   int ref;
410   object calcfunc;
411   // TODO
412 //  object movefunc;
413 };
414 
calc(const Args & args,const KigDocument &)415 ObjectImp* CompiledPythonScript::calc( const Args& args, const KigDocument& )
416 {
417   return PythonScripter::instance()->calc( *this, args );
418 }
419 
~CompiledPythonScript()420 CompiledPythonScript::~CompiledPythonScript()
421 {
422   --d->ref;
423   if ( d->ref == 0 )
424     delete d;
425 }
426 
CompiledPythonScript(Private * ind)427 CompiledPythonScript::CompiledPythonScript( Private* ind )
428   : d( ind )
429 {
430   ++d->ref;
431 }
432 
compile(const char * code)433 CompiledPythonScript PythonScripter::compile( const char* code )
434 {
435   clearErrors();
436   dict retdict;
437   bool error = false;
438   try
439   {
440     (void) PyRun_String( const_cast<char*>( code ), Py_file_input,
441                          d->mainnamespace.ptr(), retdict.ptr() );
442   }
443   catch( ... )
444   {
445     error = true;
446   };
447   error |= static_cast<bool>( PyErr_Occurred() );
448   if ( error )
449   {
450     saveErrors();
451     retdict.clear();
452   }
453 
454   // debugging stuff, removed.
455 //  std::string dictstring = extract<std::string>( str( retdict ) );
456 
457   CompiledPythonScript::Private* ret = new CompiledPythonScript::Private;
458   ret->ref = 0;
459   ret->calcfunc = retdict.get( "calc" );
460   return CompiledPythonScript( ret );
461 }
462 
CompiledPythonScript(const CompiledPythonScript & s)463 CompiledPythonScript::CompiledPythonScript( const CompiledPythonScript& s )
464   : d( s.d )
465 {
466   ++d->ref;
467 }
468 
lastErrorExceptionType() const469 std::string PythonScripter::lastErrorExceptionType() const
470 {
471   return lastexceptiontype;
472 }
473 
lastErrorExceptionValue() const474 std::string PythonScripter::lastErrorExceptionValue() const
475 {
476   return lastexceptionvalue;
477 }
478 
lastErrorExceptionTraceback() const479 std::string PythonScripter::lastErrorExceptionTraceback() const
480 {
481   return lastexceptiontraceback;
482 }
483 
calc(CompiledPythonScript & script,const Args & args)484 ObjectImp* PythonScripter::calc( CompiledPythonScript& script, const Args& args )
485 {
486   clearErrors();
487   object calcfunc = script.d->calcfunc;
488   try
489   {
490     std::vector<object> objectvect;
491     objectvect.reserve( args.size() );
492 
493     for ( int i = 0; i < (int) args.size(); ++i )
494     {
495       object o( boost::ref( *args[i] ) );
496       objectvect.push_back( o );
497     }
498 
499     handle<> argstuph( PyTuple_New( args.size() ) );
500     for ( int i = 0; i < (int) objectvect.size(); ++i )
501     {
502       /*
503        * this fixes bug https://bugs.kde.org/show_bug.cgi?id=401512
504        *
505        * it isn't completely clear whether we need XINCREF (test for null pointer)
506        * instead of INCREF.  However I think that the arguments should never be
507        * null pointers
508        *    mp
509        */
510       Py_INCREF((objectvect.begin() +i)->ptr());
511       PyTuple_SetItem( argstuph.get(), i, (objectvect.begin() +i)->ptr() );
512     };
513     tuple argstup( argstuph );
514 
515     handle<> reth( PyObject_CallObject( calcfunc.ptr(), argstup.ptr() ) );
516 //    object resulto = calcfunc( argstup );
517 //    handle<> reth( PyObject_CallObject( calcfunc.ptr(), args ) );
518     object resulto( reth );
519 
520     extract<ObjectImp&> result( resulto );
521     if( ! result.check() ) return new InvalidImp;
522     else
523     {
524       ObjectImp& ret = result();
525       return ret.copy();
526     };
527   }
528   catch( ... )
529   {
530     saveErrors();
531 
532     return new InvalidImp;
533   };
534 }
535 
saveErrors()536 void PythonScripter::saveErrors()
537 {
538   erroroccurred = true;
539   PyObject* poexctype;
540   PyObject* poexcvalue;
541   PyObject* poexctraceback;
542   PyErr_Fetch( &poexctype, &poexcvalue, &poexctraceback );
543   PyErr_NormalizeException( &poexctype, &poexcvalue, &poexctraceback );
544   handle<> exctypeh( poexctype );
545   handle<> excvalueh( poexcvalue );
546 
547   object exctype( exctypeh );
548   object excvalue( excvalueh );
549   object exctraceback;
550   if ( poexctraceback )
551   {
552     handle<> exctracebackh( poexctraceback );
553     exctraceback = object( exctracebackh );
554   }
555 
556   lastexceptiontype = extract<std::string>( str( exctype ) )();
557   lastexceptionvalue = extract<std::string>( str( excvalue ) )();
558 
559   object printexcfunc = d->mainnamespace[ "traceback" ].attr( "format_exception" );
560 
561   list tracebacklist = extract<list>( printexcfunc( exctype, excvalue, exctraceback ) )();
562   str tracebackstr( "" );
563   while ( true )
564   {
565     try {
566       str s = extract<str>( tracebacklist.pop() );
567       tracebackstr += s;
568     }
569     catch( ... )
570     {
571       break;
572     }
573   }
574 
575   lastexceptiontraceback = extract<std::string>( tracebackstr )();
576   PyErr_Clear();
577 }
578 
clearErrors()579 void PythonScripter::clearErrors()
580 {
581   PyErr_Clear();
582   lastexceptiontype.clear();
583   lastexceptionvalue.clear();
584   lastexceptiontraceback.clear();
585   erroroccurred = false;
586 }
587 
valid()588 bool CompiledPythonScript::valid()
589 {
590   return !!d->calcfunc;
591 }
592 
errorOccurred() const593 bool PythonScripter::errorOccurred() const
594 {
595   return erroroccurred;
596 }
597 
598