# HG changeset patch # User David Grundberg # Date 1241677702 -7200 # Node ID 25f49207de4696166a9415c6782222a23516b60b # Parent 5073ff11e2b6d5b478c7bc1a9cadec3bb73a70a9# Parent ae4554656fa10e088c77cfd902bbb0ae9e9bf156 Added the eval function. diff -r 5073ff11e2b6 -r 25f49207de46 ChangeLog --- a/ChangeLog Tue May 05 21:11:40 2009 +0200 +++ b/ChangeLog Thu May 07 08:28:22 2009 +0200 @@ -1,3 +1,10 @@ +2009-05-07 Jaroslav Hajek + + * exceptions.h: Added octave_parse_exception class. + * package/pytave.py: Added ParseError exception and eval method. + * pytave.cc: Extracted make_error_message from func_eval. Added + str_eval + 2009-05-05 David Grundberg * python_to_octave.cc: Function pydict_to_octmap modified. diff -r 5073ff11e2b6 -r 25f49207de46 NEWS --- a/NEWS Tue May 05 21:11:40 2009 +0200 +++ b/NEWS Thu May 07 08:28:22 2009 +0200 @@ -1,4 +1,22 @@ -Version 0.1-bzr +Version 0.1.1-bzr + +2009-05-07 + +* Added an eval function. A string of Octave code can be executed + through this function. The returned values are converted to Python + objects as with the feval function. + + In principle, this could be achieved simply using feval("eval", but + the advantages to this implementation are: + + 1. faster (avoids double call and double conversion of code string) + + 2. explicit control of printing rather than implicitly with nargout + (as in eval) + + 3. separate exception classes for parse error / execution error + +2009-05-05 * Added functionality for one-row cell arrays. The Python list is converted to a one-row cell array and vice versa. @@ -6,3 +24,4 @@ * Added functionality for structs. The Python distionary is converted to a Octave struct and vice versa. The implementation tries to be as true as possible to Octave's struct constructor. + diff -r 5073ff11e2b6 -r 25f49207de46 exceptions.cc --- a/exceptions.cc Tue May 05 21:11:40 2009 +0200 +++ b/exceptions.cc Thu May 07 08:28:22 2009 +0200 @@ -25,6 +25,7 @@ PyObject *octave_error_exception::excclass = NULL; PyObject *value_convert_exception::excclass = NULL; PyObject *object_convert_exception::excclass = NULL; + PyObject *octave_parse_exception::excclass = NULL; } diff -r 5073ff11e2b6 -r 25f49207de46 exceptions.h --- a/exceptions.h Tue May 05 21:11:40 2009 +0200 +++ b/exceptions.h Thu May 07 08:28:22 2009 +0200 @@ -55,6 +55,25 @@ }; + class octave_parse_exception { + public: + static bool init() { + excclass = PyErr_NewException( + const_cast("pytave.ParseError"), + PyExc_RuntimeError, NULL); + return excclass != NULL; + }; + static void translate_exception(octave_parse_exception const &py_ex) { + PyErr_SetString(excclass, py_ex.error.c_str()); + } + static PyObject *excclass; + + octave_parse_exception(std::string err) { error = err; }; + + private: + std::string error; + }; + class value_convert_exception { public: static bool init() { diff -r 5073ff11e2b6 -r 25f49207de46 package/pytave.py --- a/package/pytave.py Tue May 05 21:11:40 2009 +0200 +++ b/package/pytave.py Thu May 07 08:28:22 2009 +0200 @@ -22,16 +22,16 @@ import _pytave _pytave.init() -(OctaveError, ValueConvertError, ObjectConvertError) \ +(OctaveError, ValueConvertError, ObjectConvertError, ParseError) \ = _pytave.get_exceptions(); def feval(nargout, funcname, *arguments): """Executes an Octave function called funcname. - The function is set to return nargout values. Returned values are - stored in a tuple. If the nargout argument is less than or equal - to 0, an empty tuple is returned. + The function is set to return nargout values. Returned values + are stored in a tuple. If the nargout argument is less than 0, + an empty tuple is returned. M-files are searched for in the Octave path. @@ -96,6 +96,41 @@ return _pytave.feval(nargout, funcname, arguments) +def eval(nargout, code, silent=True): + + """Executes a given Octave code. + + The expression is expected to return nargout values. Returned + values are stored in a tuple. If the nargout argument is less + than 0, an empty tuple is returned. + + All normal scope and function search rules apply. If silent is + true (default), the result is not auto-printed, as if a + semicolon was appended. Otherwise, auto-printing is enabled. + + See also the Octave documentation for the builtin Octave + function eval. + + For information about returned value conversion, see + pytave.feval. + + Errors + ****** + + If the code cannot be parsed, a pytave.ParseError exception + occurs. + + Octave runtime errors are encapsulated into pytave.OctaveError + exceptions, base class RuntimeError. + + If the resulting values cannot be converted, a + pytave.ValueConvertError is raised. This exception inherits + TypeError. + + """ + + return _pytave.eval(nargout, code, silent) + def addpath(*arguments): """See Octave documentation""" return _pytave.feval(1, "addpath", arguments)[0] diff -r 5073ff11e2b6 -r 25f49207de46 pytave.cc --- a/pytave.cc Tue May 05 21:11:40 2009 +0200 +++ b/pytave.cc Thu May 07 08:28:22 2009 +0200 @@ -48,7 +48,8 @@ if (!octave_error_exception::init() || !value_convert_exception::init() - || !object_convert_exception::init()) { + || !object_convert_exception::init() + || !octave_parse_exception::init()) { PyErr_SetString(PyExc_ImportError, "_pytave: init failed"); return; } @@ -74,11 +75,45 @@ object(handle( value_convert_exception::excclass)), object(handle( - object_convert_exception::excclass))); + object_convert_exception::excclass)), + object(handle( + octave_parse_exception::excclass))); } + string make_error_message (const Octave_map& map) { + ostringstream exceptionmsg; + string message = map.stringfield("message", ""); + string identifier = map.stringfield("identifier", ""); + Cell stackCell = map.contents("stack"); + + // Trim trailing new lines + message = message.substr(0, message.find_last_not_of("\r\n") + 1); + + if (!stackCell.is_empty() && stackCell(0).is_map()) { + // The struct element is called "stack" but only contain + // info about the top frame. + Octave_map stack = stackCell(0).map_value(); + string file = stack.stringfield("file", ""); + string name = stack.stringfield("name", ""); + int line = stack.intfield("line", 1); + int column = stack.intfield("column", 2); + + exceptionmsg << file << ":" << line << ":" << column << ": "; + if (!name.empty()) + exceptionmsg << "in '" << name << "': "; + } + + if (!identifier.empty()) { + exceptionmsg << "(identifier: " << identifier << ") "; + } + exceptionmsg << message; + + return exceptionmsg.str (); + } + + boost::python::tuple func_eval(const int nargout, - const std::string &funcname, + const string &funcname, const boost::python::tuple &arguments) { octave_value_list octave_args, retval; @@ -89,7 +124,7 @@ buffer_error_messages++; Py_BEGIN_ALLOW_THREADS - retval = feval(funcname, octave_args, nargout); + retval = feval(funcname, octave_args, (nargout >= 0) ? nargout : 0); Py_END_ALLOW_THREADS if (error_state != 0) { @@ -102,45 +137,63 @@ octave_value_list lasterror = eval_string("lasterror", true, parse_status, 1); if (!lasterror.empty() && lasterror(0).is_map()) { - ostringstream exceptionmsg; - Octave_map map = lasterror(0).map_value(); - string message = map.stringfield("message", ""); - string identifier = map.stringfield("identifier", ""); - Cell stackCell = map.contents("stack"); - - // Trim trailing new lines - message = message.substr(0, message.find_last_not_of("\r\n") + 1); - - if (!stackCell.is_empty() && stackCell(0).is_map()) { - // The struct element is called "stack" but only contain - // info about the top frame. - Octave_map stack = stackCell(0).map_value(); - string file = stack.stringfield("file", ""); - string name = stack.stringfield("name", ""); - int line = stack.intfield("line", 1); - int column = stack.intfield("column", 2); - - exceptionmsg << file << ":" << line << ":" << column << ": "; - if (!name.empty()) - exceptionmsg << "in '" << name << "': "; - } - - if (!identifier.empty()) { - exceptionmsg << "(identifier: " << identifier << ") "; - } - exceptionmsg << message; - - throw octave_error_exception(exceptionmsg.str()); + string exceptionmsg = make_error_message(lasterror(0).map_value ()); + throw octave_error_exception(exceptionmsg); } else throw octave_error_exception("No Octave error available"); } - if (nargout > 0) { + if (nargout >= 0) { boost::python::tuple pytuple; octlist_to_pytuple(pytuple, retval); return pytuple; } else { - // Return () if nargout <= 0. + // Return () if nargout < 0. + return make_tuple(); + } + } + + boost::python::tuple str_eval(int nargout, + const string &code, + bool silent) { + + octave_value_list retval; + int parse_status; + + reset_error_handler(); + buffer_error_messages++; + + Py_BEGIN_ALLOW_THREADS + retval = eval_string(code, silent, parse_status, + (nargout >= 0) ? nargout : 0); + Py_END_ALLOW_THREADS + + if (parse_status != 0 || error_state != 0) { +// error_state values: +// -2 error without traceback +// -1 traceback +// 1 general error + int parse_status1 = 0; + reset_error_handler(); + octave_value_list lasterror = eval_string("lasterror", + true, parse_status1, 1); + if (!lasterror.empty() && lasterror(0).is_map()) { + string exceptionmsg = make_error_message (lasterror(0).map_value ()); + + if (parse_status != 0) + throw octave_parse_exception(exceptionmsg); + else + throw octave_error_exception(exceptionmsg); + } else + throw octave_error_exception("No Octave error available"); + } + + if (nargout >= 0) { + boost::python::tuple pytuple; + octlist_to_pytuple(pytuple, retval); + return pytuple; + } else { + // Return () if nargout < 0. return make_tuple(); } } @@ -151,6 +204,7 @@ def("init", pytave::init); def("feval", pytave::func_eval); + def("eval", pytave::str_eval); def("get_exceptions", pytave::get_exceptions); register_exception_translator( @@ -159,6 +213,9 @@ register_exception_translator( pytave::octave_error_exception::translate_exception); + register_exception_translator( + pytave::octave_parse_exception::translate_exception); + register_exception_translator( pytave::object_convert_exception::translate_exception); diff -r 5073ff11e2b6 -r 25f49207de46 test/test.py --- a/test/test.py Tue May 05 21:11:40 2009 +0200 +++ b/test/test.py Thu May 07 08:28:22 2009 +0200 @@ -86,12 +86,28 @@ except Exception, e: print "FAIL", (value,), e +def testparseerror(*value): + try: + print pytave.eval(*value); + print "FAIL:", (value,) + except pytave.ParseError: + pass + except Exception, e: + print "FAIL", (value,), e + def testvalueok(*value): try: pytave.feval(1, *value); except Exception, e: print "FAIL", (value,), e +def testevalexpect(numargout, code, expectations): + try: + results = pytave.eval(numargout, code); + if results != expectations: + print "FAIL: eval: ", code, " because", results, " != ", expectations, "," + except Exception, e: + print "FAIL: eval:", code, ":", e def testcellinvariant(value): pass @@ -200,4 +216,7 @@ if result.shape != (3, 1): print "FAIL: expected 3x1 matrix" - +testparseerror(1, "endfunction") +testevalexpect(1, "2 + 2", (4,)) +testevalexpect(0, "{2}", ([2],)) +testevalexpect(2, "struct('foo', 2)", ({'foo': [2]},))