changeset 38:25f49207de46 lp-trunk

Added the eval function.
author David Grundberg <individ@acc.umu.se>
date Thu, 07 May 2009 08:28:22 +0200
parents 5073ff11e2b6 (current diff) ae4554656fa1 (diff)
children 380444ea0f28
files
diffstat 7 files changed, 198 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- 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  <highegg@gmail.com>
+
+	* 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  <individ@acc.umu.se>
 
 	* python_to_octave.cc: Function pydict_to_octmap modified.
--- 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.
+
--- 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;
 
 }
 
--- 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<char*>("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() {
--- 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]
--- 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<PyObject>(
                                   value_convert_exception::excclass)),
                         object(handle<PyObject>(
-                                  object_convert_exception::excclass)));
+                                  object_convert_exception::excclass)),
+                        object(handle<PyObject>(
+                                  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<pytave::pytave_exception>(
@@ -159,6 +213,9 @@
    register_exception_translator<pytave::octave_error_exception>(
       pytave::octave_error_exception::translate_exception);
 
+   register_exception_translator<pytave::octave_parse_exception>(
+      pytave::octave_parse_exception::translate_exception);
+
    register_exception_translator<pytave::object_convert_exception>(
       pytave::object_convert_exception::translate_exception);
 
--- 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]},))