changeset 272:1446812ec1de

Merged in genuinelucifer/pytave_main (pull request #29) Convert numeric value to long instead of int to avoid overflow (fixes issue #40)
author Mike Miller <mike@mtmxr.com>
date Fri, 29 Jul 2016 13:33:56 -0700
parents 2cb6e2824776 (diff) 0a4f620da178 (current diff)
children ecca6a885ffd
files pyeval.cc
diffstat 9 files changed, 282 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/@py/py.m	Fri Jul 29 13:33:56 2016 -0700
@@ -0,0 +1,30 @@
+## Copyright (C) 2016 Mike Miller
+##
+## This file is part of Pytave.
+##
+## Pytave is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## Pytave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Pytave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {} py
+## @deftypefnx {} {} py.@var{pyname}
+## Get the value of a Python object or call a Python function.
+## @end deftypefn
+
+function p = py ()
+  p = class (struct (), "py");
+endfunction
+
+%!assert (py.math.sqrt (2), sqrt (2))
+%!assert (ischar (py.sys.version))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/@py/subsref.m	Fri Jul 29 13:33:56 2016 -0700
@@ -0,0 +1,56 @@
+## Copyright (C) 2016 Mike Miller
+##
+## This file is part of Pytave.
+##
+## Pytave is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## Pytave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Pytave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {} subsref (@var{x}, @var{idx})
+## Implements Python name lookup via dot indexing.
+## @enddeftypefn
+
+function varargout = subsref (x, idx)
+
+  if (nargin != 2)
+    print_usage ();
+  endif
+
+  if (! isa (x, "py"))
+    error ("py: invalid call to subsref");
+  endif
+
+  if (isempty (idx) || ! isstruct (idx))
+    error ("py: invalid call to subsref without indices");
+  endif
+
+  type = idx(1).type;
+  subs = idx(1).subs;
+
+  if (type != ".")
+    error ("py: invalid indexing type");
+  endif
+
+  y = pycall ("__import__", subs);
+
+  if (numel (idx) > 1)
+    y = subsref (y, idx(2:end));
+  endif
+
+  is_none = pyeval ("lambda x: x is None");
+  if (nargout > 0 || ! pycall (is_none, y))
+    varargout{1} = y;
+  endif
+
+endfunction
--- a/@pyobject/dummy.m	Thu Jul 28 11:50:29 2016 -0700
+++ b/@pyobject/dummy.m	Fri Jul 29 13:33:56 2016 -0700
@@ -32,12 +32,17 @@
 ##   @result{} ans =
 ##     @{
 ##       [1,1] = bit_length
-##       [1,2] = conjugate
-##       [1,3] = denominator
-##       [1,4] = imag
-##       [1,5] = numerator
-##       [1,6] = real
-##      @}
+##       [2,1] = conjugate
+##     @}
+##
+## sort (fieldnames (g))
+##   @result{} ans =
+##     @{
+##       [1,1] = denominator
+##       [2,1] = imag
+##       [3,1] = numerator
+##       [4,1] = real
+##     @}
 ##
 ## g.numerator
 ##   @result{} ans =  6
--- a/@pyobject/methods.m	Thu Jul 28 11:50:29 2016 -0700
+++ b/@pyobject/methods.m	Fri Jul 29 13:33:56 2016 -0700
@@ -21,8 +21,8 @@
 ## @defmethod @@pyobject methods (@var{x})
 ## List the properties/callables of a Python object.
 ##
-## Returns a cell array of strings, the names of the properties
-## and ``callables'' of @var{x}.
+## Returns a cell array of strings, the names of the ``callables''
+## of @var{x}.
 ##
 ## Example:
 ## @example
@@ -30,7 +30,7 @@
 ## pyexec ("import os")
 ## os = pyeval ("os");
 ## methods (os)
-##   @print{} Methods for module os:
+##   @print{} Methods for Python module 'os':
 ##   @print{} ...
 ##   @print{} chdir ...
 ##   @print{} ...
@@ -38,7 +38,7 @@
 ##   @result{} x =
 ##     @{
 ##       [1,1] = ...
-##       [1,2] = ...
+##       [2,1] = ...
 ##        ...  = chdir
 ##        ...  = getenv
 ##        ...
@@ -46,8 +46,11 @@
 ## @end group
 ## @end example
 ##
+## To get the properties (non-callables) of an object,
+## @pxref{@@pyobject/fieldnames}.
+##
 ## Note that if you instead want the methods implemented by
-## the Octave class @code{@@pyobject}, use can always do:
+## the Octave class @code{@@pyobject}, you can always do:
 ## @example
 ## @group
 ## methods pyobject
@@ -58,15 +61,13 @@
 ## @end group
 ## @end example
 ##
-## @seealso{methods}
+## @seealso{methods, @@pyobject/fieldnames}
 ## @end defmethod
 
 
 function mtds = methods (x)
 
-  # filter the output of `dir(x)`
-  # (to get callable methods only:
-  # [a for a in dir(x) if callable(getattr(x, a)) and not a.startswith('__')]
+  # filter the output of `dir(x)` to get callable methods only
   cmd = sprintf (["[a for x in (__InOct__['%s'],) for a in dir(x) " ...
                   " if callable(getattr(x, a))" ...
                   " and not a.startswith('__')]"],
@@ -81,17 +82,16 @@
 
     if (pycall (is_module, x))
       modulename = pycall ("getattr", x, "__name__");
-      printf ("Methods for module %s:\n", modulename);
+      printf ("Methods for Python module '%s':\n", modulename);
     else
       ## FIXME: should be `class (x)`
       classref = pycall ("getattr", x, "__class__");
       classname = pycall ("getattr", classref, "__name__");
-      ## FIXME: indicate that this is a Python class?
-      printf ("Methods for class %s:\n", classname);
+      printf ("Methods for Python class '%s':\n", classname);
     endif
     disp (list_in_columns (mtds_list));
   else
-    mtds = mtds_list;
+    mtds = mtds_list(:);
   endif
 
 endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/@pyobject/subsasgn.m	Fri Jul 29 13:33:56 2016 -0700
@@ -0,0 +1,123 @@
+## Copyright (C) 2016 Colin B. Macdonald
+##
+## This file is part of Pytave.
+##
+## Pytave is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; either version 3 of the License,
+## or (at your option) any later version.
+##
+## This software is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty
+## of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
+## the GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public
+## License along with this software; see the file COPYING.
+## If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @documentencoding UTF-8
+## @defop  Method   @@pyobject subsasgn (@var{x}, @var{idx}, @var{rhs})
+## @defopx Operator @@pyobject {@var{x}.@var{property} =} {@var{rhs}}
+## @defopx Operator @@pyobject {@var{x}@{@var{i}@} =} {@var{rhs}}
+## @defopx Operator @@pyobject {@var{x}@{@var{i}, @var{j}, @dots{}@} =} {@var{rhs}}
+## @defopx Operator @@pyobject {@var{x}@{@var{a}@} =} {@var{rhs}}
+## Indexed assignment to Python objects.
+##
+## @seealso{@@pyobject/subsref}
+## @end defop
+
+function r = subsasgn(x, idx, rhs)
+
+  if (nargin != 3)
+    print_usage ();
+  endif
+
+  ## If rhs is a pyobject but x is not, dispatch to the builtin subsasgn
+  if (! isa (x, "pyobject"))
+    r = builtin ("subsasgn", x, idx, rhs);
+    return;
+  endif
+
+  switch idx.type
+    case "."
+      assert (ischar (idx.subs))
+      pycall ("setattr", x, idx.subs, rhs);
+      r = x;
+
+    case "{}"
+      ## XXX: doesn't support slices or anything like that yet
+
+      ## Subtract one from index: do this for lists, numpy arrays, etc
+      pyexec ("import collections")
+      pyexec ("import numpy")
+      x_is_list = pycall (pyeval (
+        "lambda x: isinstance(x, (collections.Sequence, numpy.ndarray))"),
+        x);
+      for i = 1:length (idx.subs)
+        j = idx.subs{i};
+        if (x_is_list && isindex (j) && isnumeric (j))
+          idx.subs{i} = cast (j, class (sizemax ())) - 1;
+        endif
+      endfor
+
+      if (isscalar (idx.subs))
+        ind = idx.subs{1};
+      else
+        error ("not implemented, waiting on #26, #27")
+      endif
+
+      xsi = pycall ("getattr", x, "__setitem__");   # x.__setitem__
+      pycall (xsi, ind, rhs);
+      r = x;
+
+    otherwise
+      idx
+      rhs
+      error ("@pyobject/subsasgn: not implemented")
+  endswitch
+endfunction
+
+
+%!test
+%! pyexec ("class MyClass: a = 1")
+%! t = pyeval ("MyClass()");
+%! t.b = 6;
+%! assert (t.b, 6)
+
+%!test
+%! % list indexing
+%! pyexec ("L = [10, 20]")
+%! L = pyobject.fromPythonVarName ("L");
+%! L{2} = "Octave";
+%! assert (length (L) == 2)
+%! assert (L{1}, 10)
+%! assert (L{2}, "Octave")
+
+%!test
+%! % dict assignment, adding new keys
+%! pyexec ("d = dict()")
+%! d = pyobject.fromPythonVarName ("d");
+%! d{"a"} = 3;
+%! d{"b"} = 4;
+%! assert (d{"a"}, 3)
+%! assert (d{"b"}, 4)
+
+%!test
+%! % dict assignment, update existing key
+%! pyexec ("d = {'a':1}")
+%! d = pyobject.fromPythonVarName ("d");
+%! d{"a"} = 3;
+%! assert (d{"a"}, 3)
+
+%!test
+%! % dict assignment, other keys (e.g., Issue #10).
+%! pyexec ("d = dict()")
+%! d = pyobject.fromPythonVarName ("d");
+%! d{"5"} = 10;
+%! d{5.5} = 11;
+%! d{5} = 12;
+%! assert (d{"5"}, 10)
+%! assert (d{5.5}, 11)
+%! assert (d{5}, 12)
--- a/@pyobject/subsref.m	Thu Jul 28 11:50:29 2016 -0700
+++ b/@pyobject/subsref.m	Fri Jul 29 13:33:56 2016 -0700
@@ -44,24 +44,34 @@
       r = pycall ("getattr", x, t.subs);
 
     case "{}"
-      subsstrs = {};
-      for j = 1:length (t.subs)
-        thissub = t.subs{j};
-        if (ischar (thissub) && strcmp (thissub, ":"))
-          subsstrs{j} = ":";
-        elseif (ischar (thissub))
-          subsstrs{j} = ["'" thissub "'"];
-        elseif (isnumeric (thissub) && isscalar (thissub))
-          ## note: python indexed from 0
-          subsstrs{j} = num2str (thissub - 1);
-        else
-          thissub
-          error ("@pyobject/subsref: subs not supported")
+      ## Subtract one from index: do this for lists, numpy arrays, etc
+      pyexec ("import collections")
+      pyexec ("import numpy")
+      x_is_list = pycall (pyeval (
+        "lambda x: isinstance(x, (collections.Sequence, numpy.ndarray))"),
+        x);
+      for i = 1:length(t.subs)
+        j = t.subs{i};
+        if (isindex (j) && isnumeric (j) && x_is_list)
+          t.subs{i} = cast (j, class (sizemax ())) - 1;
         endif
       endfor
-      s = ["[" strjoin(subsstrs, ", ") "]"];
-      ## XXX: can we use .__getitem__ here?
-      r = pyeval (sprintf ("__InOct__['%s']%s", x.id, s));
+
+      if (isscalar (t.subs))
+        ind = t.subs{1};
+      else
+        ## XXX: after #26, #27, I think its just:
+        #ind = pycall ("tuple", t.subs);
+        pyexec (["global _temp\n" ...
+                 "def pystoretemp(x):\n" ...
+                 "    global _temp\n" ...
+                 "    _temp = x"]);
+        pycall ("pystoretemp", t.subs);
+        pyexec ("_temp = tuple(_temp[0])");
+        ind = pyobject.fromPythonVarName ("_temp");
+      endif
+      gi = pycall ("getattr", x, "__getitem__");   # x.__getitem__
+      r = pycall (gi, ind);
 
     otherwise
       t
@@ -88,8 +98,8 @@
 %! assert (L{1}, 10)
 %! assert (L{2}, 20)
 
-%!test
-%! % list indexing
+%!xtest
+%! % list indexing, slice
 %! pyexec ("L = [10, 20, [30, 40]]")
 %! L = pyobject.fromPythonVarName ("L");
 %! L2 = L{:};
@@ -127,13 +137,26 @@
 %! d = pyobject.fromPythonVarName ("d");
 %! assert (d{6}, 42)
 
-%!xtest
-%! % dict: integer key should not subtract one (FIXME: Issue #10)
+%!test
+%! % dict: integer key should not subtract one
 %! pyexec ("d = {5:40, 6:42}")
 %! d = pyobject.fromPythonVarName ("d");
 %! assert (d{6}, 42)
 
 %!test
+%! % dict: floating point keys should work
+%! pyexec ("d = {5.5:'ok'}")
+%! d = pyobject.fromPythonVarName ("d");
+%! assert (d{5.5}, "ok")
+
+%!test
+%! % dict: make sure key ":" doesn't break anything
+%! pyexec ("d = {'a':1, ':':2}")
+%! d = pyobject.fromPythonVarName ("d");
+%! assert (d{'a'}, 1)
+%! assert (d{':'}, 2)
+
+%!test
 %! % method call with args
 %! s = pyeval ("set({1, 2})");
 %! s.add (42)
--- a/exceptions.cc	Thu Jul 28 11:50:29 2016 -0700
+++ b/exceptions.cc	Fri Jul 29 13:33:56 2016 -0700
@@ -52,6 +52,7 @@
 
     PyObject *ptype, *pvalue, *ptraceback;
     PyErr_Fetch (&ptype, &pvalue, &ptraceback);
+    PyErr_NormalizeException (&ptype, &pvalue, &ptraceback);
     std::string message;
 
     try
--- a/pycall.cc	Thu Jul 28 11:50:29 2016 -0700
+++ b/pycall.cc	Fri Jul 29 13:33:56 2016 -0700
@@ -278,11 +278,11 @@
 %! endfor
 
 %!error <argument type conversion>
-%! pyexec ("def intwrapper(x):\n    return int(x)\n");
+%! pyexec ("def intwrapper(x): return int(x)");
 %! pycall ("intwrapper", ftp ());
 
 %!test
-%! pyexec ("def pyfunc(x):\n    return 2*x");
+%! pyexec ("def pyfunc(x): return 2*x");
 %! z = pycall ("pyfunc", [20 20]);
 %! assert (z, [40 40])
 
@@ -296,7 +296,7 @@
 %! assert (pycall ("pyfunc", 10), 10)
 
 %!error <NameError>
-%! pyexec ("def raiseException ():\n  raise NameError ('oops')")
+%! pyexec ("def raiseException(): raise NameError('oops')")
 %! pycall ("raiseException")
 
 ## None as a return value
--- a/pyeval.cc	Thu Jul 28 11:50:29 2016 -0700
+++ b/pyeval.cc	Fri Jul 29 13:33:56 2016 -0700
@@ -160,6 +160,6 @@
 %! assert (z{4}{2}{2}, 422)
 
 %!error <NameError>
-%! pyexec ("def raiseException ():\n  raise NameError ('oops')")
-%! pyeval ("raiseException ()")
+%! pyexec ("def raiseException(): raise NameError ('oops')")
+%! pyeval ("raiseException()")
 */