# HG changeset patch # User Jaroslav Hajek # Date 1243240211 -7200 # Node ID 824354efaa1ab80346cff584787d846bd5709b47 # Parent 380444ea0f280df879c64b1ef3c81c632a6e8a76 implement direct variable manipulation and local scoping diff -r 380444ea0f28 -r 824354efaa1a ChangeLog --- 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 + + * 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 * ax_octave_float.m4: New file. diff -r 380444ea0f28 -r 824354efaa1a exceptions.cc --- 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; } diff -r 380444ea0f28 -r 824354efaa1a exceptions.h --- 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("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 */ diff -r 380444ea0f28 -r 824354efaa1a octave_to_python.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); } diff -r 380444ea0f28 -r 824354efaa1a package/pytave.py --- 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): + + + is equivalent to: + + def myfunc(a,b): + try: + pytave.push_scope() + + finally: + pytave.pop_scope() + """ + return _local_scope(func) + # Emacs # Local Variables: # fill-column:70 diff -r 380444ea0f28 -r 824354efaa1a pytave.cc --- 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 #include #include +#include +#include +#include #include #include @@ -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( object_convert_exception::excclass)), object(handle( - octave_parse_exception::excclass))); + octave_parse_exception::excclass)), + object(handle( + 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( @@ -222,6 +298,9 @@ register_exception_translator( pytave::value_convert_exception::translate_exception); + register_exception_translator( + pytave::variable_name_exception::translate_exception); + } /* }}} */ /* Emacs diff -r 380444ea0f28 -r 824354efaa1a python_to_octave.h --- 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); } diff -r 380444ea0f28 -r 824354efaa1a test/test.py --- 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)