changeset 40:824354efaa1a

implement direct variable manipulation and local scoping
author Jaroslav Hajek <highegg@gmail.com>
date Mon, 25 May 2009 10:30:11 +0200
parents 380444ea0f28
children de24e11a4c35
files ChangeLog exceptions.cc exceptions.h octave_to_python.h package/pytave.py pytave.cc python_to_octave.h test/test.py
diffstat 8 files changed, 271 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat May 09 15:52:59 2009 +0200
+++ b/ChangeLog	Mon May 25 10:30:11 2009 +0200
@@ -1,3 +1,24 @@
+2009-05-13  Jaroslav Hajek  <highegg@gmail.com>
+
+	* exceptions.h (variable_name_exception): New exception class.
+	* exceptions.cc: Initialize it.
+	* octave_to_python.h (octvalue_to_pyobj): Declare prototype.
+	* pytave.cc (init): Init variable_name_exception.
+	(getvar): New function.
+	(setvar): New function.
+	(isvar): New function.
+	(push_scope): New function.
+	(pop_scope): New function.
+	* package/pytave.py (__init__): Init VarNameError.
+	(getvar): New function.
+	(setvar): New function.
+	(isvar): New function.
+	(push_scope): New function.
+	(pop_scope): New function.
+	(_local_scope): New decorator helper class.
+	(local_scope): New decorator.
+	* test/test.py: Include new tests.
+
 2009-05-09  David Grundberg  <individ@acc.umu.se>
 
 	* ax_octave_float.m4: New file.
--- a/exceptions.cc	Sat May 09 15:52:59 2009 +0200
+++ b/exceptions.cc	Mon May 25 10:30:11 2009 +0200
@@ -26,6 +26,7 @@
 	PyObject *value_convert_exception::excclass = NULL;
 	PyObject *object_convert_exception::excclass = NULL;
 	PyObject *octave_parse_exception::excclass = NULL;
+	PyObject *variable_name_exception::excclass = NULL;
 
 }
 
--- a/exceptions.h	Sat May 09 15:52:59 2009 +0200
+++ b/exceptions.h	Mon May 25 10:30:11 2009 +0200
@@ -113,6 +113,26 @@
          std::string error;
    };
 
+   class variable_name_exception {
+      public:
+         static bool init() {
+            excclass = PyErr_NewException(
+               const_cast<char*>("pytave.VarNameError"),
+               PyExc_RuntimeError, NULL);
+            return excclass != NULL;
+         };
+         static void translate_exception(variable_name_exception const &py_ex) {
+            PyErr_SetString(excclass, py_ex.error.c_str());
+         }
+         static PyObject *excclass;
+
+         variable_name_exception(std::string err) { error = err; };
+
+      private:
+         std::string error;
+
+   };
+
 }
 
 #endif /* PYTAVE_EXCEPTIONS_H */
--- a/octave_to_python.h	Sat May 09 15:52:59 2009 +0200
+++ b/octave_to_python.h	Mon May 25 10:30:11 2009 +0200
@@ -21,6 +21,8 @@
 #define OCTAVE_TO_PYTHON_H
 
 namespace pytave {
+   void octvalue_to_pyobj(boost::python::object &py_object,
+                          const octave_value& octvalue);
    void octlist_to_pytuple(boost::python::tuple &python_tuple,
                            const octave_value_list &octave_list);
 }
--- a/package/pytave.py	Sat May 09 15:52:59 2009 +0200
+++ b/package/pytave.py	Mon May 25 10:30:11 2009 +0200
@@ -1,6 +1,7 @@
 # -*- coding:utf-8 -*-
 #
 # Copyright 2008 David Grundberg, Håkan Fors Nilsson
+# Copyright 2009 Jaroslav Hajek, VZLU Prague
 #
 # This file is part of Pytave.
 #
@@ -22,8 +23,8 @@
 import _pytave
 
 _pytave.init()
-(OctaveError, ValueConvertError, ObjectConvertError, ParseError) \
-				  = _pytave.get_exceptions();
+(OctaveError, ValueConvertError, ObjectConvertError, ParseError, \
+ VarNameError) = _pytave.get_exceptions();
 
 def feval(nargout, funcname, *arguments):
 
@@ -143,6 +144,100 @@
 	"""See Octave documentation"""
 	return _pytave.feval(1, "path", paths)[0]
 
+def getvar(name, fglobal = False):
+	 """Queries a variable by name from the current Octave scope.
+	 This is pretty much equivalent to calling eval(name), but is
+	 much faster because the Octave parser is bypassed. 
+	 
+	 global specifies that a global variable should be looked up;
+	 otherwise, local variable (in the current scope) is always
+	 searched for.
+
+	 If the variable is not defined, VarNameError exception is raised.
+	 """
+	 return _pytave.getvar(name, fglobal)
+
+def setvar(name, val, fglobal = False):
+	 """Sets a variable by name from the current Octave scope.
+	 It is quite fast because the Octave parser is bypassed. 
+
+	 global specifies that a global variable should be assigned to;
+	 otherwise, local variable (in the current scope) is always
+	 searched for.
+
+	 If the variable is not defined, a new variable is created.
+
+	 If the variable name is not valid, VarNameError exception is raised.
+	 """
+
+	 return _pytave.setvar(name, val, fglobal)
+
+def isvar(name, fglobal = False):
+	 """Checks whether a variable exists in the current Octave scope.
+	 It is quite fast because the Octave parser is bypassed. 
+
+	 global specifies that a global variable should be looked up;
+	 otherwise, local variable (in the current scope) is always
+	 searched for.
+
+	 If the variable is defined, returns True, otherwise returns False.
+	 """
+
+	 return _pytave.isvar(name, fglobal)
+
+def push_scope():
+	 """Creates a new anonymous local variable scope on the Octave call
+	 stack and sets it as the current Octave scope. Subsequent eval,
+	 getvar and setvar calls will affect variables within this scope.
+
+	 This is useful to do if you call the Octave engine from within
+	 multiple Python functions, to prevent them from hampering each
+	 other's data. As such, it is advisable to always create a local
+	 scope in a production code.
+	 """
+	 return _pytave.push_scope()
+
+def pop_scope():
+	 """Pops the current active scope (created previously by
+	 push_scope) off the Octave call stack. The previous scope
+	 will become the active scope.
+
+	 If already at the top-level scope, this function does nothing.
+	 """
+	 _pytave.pop_scope()
+ 
+class _local_scope:
+	 def __init__(self, func):
+		  self.func = func
+		  self.__name__ = func.__name__
+		  self.__doc__ = func.__doc__
+
+	 def __call__(self, *args, **kwargs):
+		  try:
+				_pytave.push_scope()
+				return self.func(*args, **kwargs)
+		  finally:
+				_pytave.pop_scope()
+
+def local_scope(func):
+	 """Decorates a function to use local Octave scope.
+	 Example:
+
+	 @pytave.local_scope
+	 def myfunc(a,b):
+		  <function body>
+
+	 is equivalent to:
+
+	 def myfunc(a,b):
+		  try:
+				pytave.push_scope()
+				<function body>
+		  finally:
+			  pytave.pop_scope()
+	 """
+	 return _local_scope(func)
+
 # Emacs
 #	Local Variables:
 #	fill-column:70
--- a/pytave.cc	Sat May 09 15:52:59 2009 +0200
+++ b/pytave.cc	Mon May 25 10:30:11 2009 +0200
@@ -1,5 +1,6 @@
 /*
  *  Copyright 2008 David Grundberg, Håkan Fors Nilsson
+ *  Copyright 2009 Jaroslav Hajek, VZLU Prague
  *
  *  This file is part of Pytave.
  *
@@ -28,6 +29,9 @@
 #include <octave/octave.h>
 #include <octave/ov.h>
 #include <octave/parse.h>
+#include <octave/utils.h>
+#include <octave/symtab.h>
+#include <octave/toplev.h>
 
 #include <iostream>
 #include <sstream>
@@ -49,7 +53,8 @@
       if (!octave_error_exception::init()
           || !value_convert_exception::init()
           || !object_convert_exception::init()
-          || !octave_parse_exception::init()) {
+          || !octave_parse_exception::init()
+          || !variable_name_exception::init ()) {
          PyErr_SetString(PyExc_ImportError, "_pytave: init failed");
          return;
       }
@@ -77,7 +82,9 @@
                         object(handle<PyObject>(
                                   object_convert_exception::excclass)),
                         object(handle<PyObject>(
-                                  octave_parse_exception::excclass)));
+                                  octave_parse_exception::excclass)),
+                        object(handle<PyObject>(
+                                  variable_name_exception::excclass)));
    }
 
    string make_error_message (const Octave_map& map) {
@@ -197,6 +204,70 @@
          return make_tuple();
       }
    }
+
+   boost::python::object getvar(const string& name,
+                                bool global) {
+      octave_value val;
+
+      if (global)
+         val = symbol_table::global_varval(name);
+      else
+         val = symbol_table::varval(name);
+
+      if (val.is_undefined()) {
+         throw variable_name_exception (name + " not defined in current scope");
+      }
+
+      boost::python::object pyobject;
+      octvalue_to_pyobj(pyobject, val);
+
+      return pyobject;
+   }
+
+   void setvar(const string& name, 
+               const boost::python::object& pyobject,
+               bool global) {
+      octave_value val;
+
+      if (!valid_identifier(name)) {
+         throw variable_name_exception (name + " is not a valid identifier");
+      }
+
+      pyobj_to_octvalue(val, pyobject);
+
+      if (global)
+         symbol_table::global_varref(name) = val;
+      else
+         symbol_table::varref(name) = val;
+   }
+
+   bool isvar(const string& name, bool global) {
+      bool retval;
+
+      if (global)
+         retval = symbol_table::is_global (name);
+      else
+         retval = symbol_table::is_local_variable (name);
+
+      return retval;
+   }
+
+   int push_scope() {
+      symbol_table::scope_id local_scope = symbol_table::alloc_scope();
+      symbol_table::set_scope(local_scope);
+      octave_call_stack::push(local_scope);
+      return local_scope;
+   }
+
+   void pop_scope () {
+      symbol_table::scope_id curr_scope = symbol_table::current_scope();
+      if (curr_scope != symbol_table::top_scope())
+         {
+            symbol_table::erase_scope(curr_scope);
+            octave_call_stack::pop();
+         }
+   }
+      
 } /* namespace pytave }}} */
 
 BOOST_PYTHON_MODULE(_pytave) { /* {{{ */
@@ -205,6 +276,11 @@
    def("init", pytave::init);
    def("feval", pytave::func_eval);
    def("eval", pytave::str_eval);
+   def("getvar", pytave::getvar);
+   def("setvar", pytave::setvar);
+   def("isvar", pytave::isvar);
+   def("push_scope", pytave::push_scope);
+   def("pop_scope", pytave::pop_scope);
    def("get_exceptions", pytave::get_exceptions);
 
    register_exception_translator<pytave::pytave_exception>(
@@ -222,6 +298,9 @@
    register_exception_translator<pytave::value_convert_exception>(
       pytave::value_convert_exception::translate_exception);
 
+   register_exception_translator<pytave::variable_name_exception>(
+      pytave::variable_name_exception::translate_exception);
+
 } /* }}} */
 
 /* Emacs
--- a/python_to_octave.h	Sat May 09 15:52:59 2009 +0200
+++ b/python_to_octave.h	Mon May 25 10:30:11 2009 +0200
@@ -21,6 +21,8 @@
 #define PYTHON_TO_OCTAVE_H
 
 namespace pytave {
+   void pyobj_to_octvalue(octave_value &oct_value,
+                          const boost::python::object &py_object);
    void pytuple_to_octlist(octave_value_list &octave_list,
                            const boost::python::tuple &python_tuple);
 }
--- a/test/test.py	Sat May 09 15:52:59 2009 +0200
+++ b/test/test.py	Mon May 25 10:30:11 2009 +0200
@@ -111,6 +111,49 @@
 def testcellinvariant(value):
 	pass
 
+def testsetget(name,value):
+    try:
+	pytave.setvar(name,value)
+	result, = pytave.feval(1, "isequal", value, pytave.getvar(name))
+	if not result:
+	    print "FAIL: set/get: ", name," -> ",value," results diverged"
+    except Exception, e:
+	print "FAIL: set/get: ", name, ":"
+
+def testvarnameerror(name):
+    try:
+	pytave.setvar(name)
+	print "FAIL: ", name
+    except pytave.VarNameError:
+	pass
+    except Exception, e:
+	print "FAIL: ", name
+
+def testlocalscope(x):
+
+    @pytave.local_scope
+    def sloppy_factorial(x):
+	pytave.setvar("x",x)
+	xm1, = pytave.eval(1,"x-1")
+	if xm1 > 0:
+	    fxm1 = sloppy_factorial(xm1)
+	else:
+	    fxm1 = 1
+	pytave.setvar("fxm1",fxm1)
+	fx, = pytave.eval(1,"x * fxm1")
+	return fx
+
+    try:
+	fx = sloppy_factorial(x)
+	fx1 = 1.0
+	for k in range(1,x+1):
+	    fx1 = k * fx1
+	if fx != fx1:
+	    print 'FAIL: testlocalscope: result incorrect'
+    except Exception, e:
+	print "FAIL: testlocalscope:", (x,), e
+
+
 testequal('a')
 
 
@@ -220,3 +263,7 @@
 testevalexpect(1, "2 + 2", (4,))
 testevalexpect(0, "{2}", ([2],))
 testevalexpect(2, "struct('foo', 2)", ({'foo': [2]},))
+
+testsetget("xxx", [1,2,3])
+
+testlocalscope(5)