From 1a56eedfe6b26ba4d97ea20111d1dc544b14e823 Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Mon, 21 May 2018 03:18:48 +0300 Subject: [PATCH 01/10] ported to py3 --- HACKING.rst | 9 +- INSTALL | 65 +--- NEWS.rst | 4 + SMOP.rst | 767 ++++++++++++++++++++----------------- setup.py | 7 +- smop/Makefile | 42 +- smop/backend.py | 7 +- smop/core.py | 732 ----------------------------------- smop/lexer.py | 11 +- smop/libscripts/.gitignore | 1 - smop/libscripts/Makefile | 36 -- smop/m.pl | 132 ------- smop/main.py | 67 +--- smop/mybench.m | 450 ---------------------- smop/node.py | 5 +- smop/options.py | 103 ++--- smop/parse.py | 8 +- smop/recipes.py | 21 +- smop/resolve.pl | 127 ------ smop/resolve.py | 71 ++-- smop/rules.pl | 32 -- smop/test_primes.py | 4 +- 22 files changed, 597 insertions(+), 2104 deletions(-) delete mode 100644 smop/core.py delete mode 100644 smop/libscripts/.gitignore delete mode 100644 smop/libscripts/Makefile delete mode 100644 smop/m.pl delete mode 100644 smop/mybench.m delete mode 100644 smop/resolve.pl delete mode 100644 smop/rules.pl diff --git a/HACKING.rst b/HACKING.rst index 07b6863d..77d87ee6 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -1,3 +1,10 @@ +node.py resolve.py + +class method +node.ident self.ident + + + ========= HACKING ========= @@ -8,7 +15,7 @@ HACKING | | | | | | A. Base-one indexing | yes | yes | no | +-----------------------------------------+-------+-------+-------+ -| | | | | +| | | | | | B. Columns-first data layout | yes | yes | no | +-----------------------------------------+-------+-------+-------+ | C. Auto-expanding arrays | yes | no * | yes | diff --git a/INSTALL b/INSTALL index d531e875..6e153a69 100644 --- a/INSTALL +++ b/INSTALL @@ -1,74 +1,29 @@ smop -- matlab to python compiler -Installation with pip -===================== - -Install pip - - sudo apt-get install python-pip - -Install smop - - pip install smop --user - -or - - sudo pip install smop - -Done. - Installation with easy_install ============================== This is the preferred option, because easy_install takes care of -everything -- finding the dowinload site, and installing the -dependences. If you don't have easy_install, this is the perfect time -to install it. It is a part of setuptools package, so for linux users - - sudo apt-get install python-setuptools - -For other operating systems, see - - http://pypi.python.org/pypi/setuptools +everything -- finding the dowinload site, and installing the dependences +(numpy and networkx). If you don't have easy_install, this is the +perfect time to install it. It is a part of setuptools package located +at http://pypi.python.org/pypi/setuptools. -Now create directory ~/.local/bin and make sure it is in your path. -Install smop: - - easy_install --user smop - -Alternatively, install as root - - sudo easy_install smop - -Installing the dependences -========================== - -From time to time, one of the projects that we depend on, fails to -install using easy_install (as of Nov 6, 2016 scipy). Don't panic. -Manually install whatever projects are missing. - - sudo apt-get install python-setuptools python-ply python-numpy python-scipy +$ easy_install smop Binary Windows installer ======================== Download it from github then run it, but you must make sure you have -installed the dependences. +installed the dependences -- numpy and networkx. Installation from sources ========================= -1. Clone git repository - - git clone https://github.com/victorlei/smop.git - -2. Install the dependences. This was tested on ubuntu and linuxmint - - sudo apt-get install python-setuptools python-ply python-numpy python-scipy - -3. Run the install script - - sudo python setup.py install [--user] +$ tar zxvf smop.tar.gz +$ cd smop +$ python setup.py install [--user] +If you use the --user option, don't forget to put your .local/bin into the search path (unix only). diff --git a/NEWS.rst b/NEWS.rst index d5caadf0..fc153114 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -2,6 +2,10 @@ Release notes ============= +December 24, 2017 + Version 0.38 will contain the port to + python3. + December 3, 2016 Version 0.34 is out diff --git a/SMOP.rst b/SMOP.rst index 7478d28b..4e03cbb6 100644 --- a/SMOP.rst +++ b/SMOP.rst @@ -1,423 +1,502 @@ +.. contents:: + +============================ Lexical analysis and parsing ============================ -A. Reserved words - Reserved words are not reserved when used as fields. - So ``return=1`` is illegal, but ``foo.return=1`` is fine. - -#. Round, square, and curly brackets - In Python and in C it's simple -- function are called - using parentheses, and array subscripted with square - brackets. In Matlab and in Fortran both are done with - parentheses, so there is no visual difference between - function call ``foo(x)`` and indexing array ``foo``. - -#. Handling the white space. - There are four kinds of ws, each recognized by a dedicated - rule: NEWLINE, COMMENT, ELLIPSIS, and SPACES. Only NEWLINE - returns a token, the three others silently discard their input. - NEWLINE collects adjacent ``\n`` characters and returns a - single SEMI token:: - - def t_NEWLINE(t): - r'\n+' - t.lexer.lineno += len(t.value) - if not t.lexer.parens and not t.lexer.braces: - t.value = ";" - t.type = "SEMI" - return t - - Comments come in two flavors -- regular and MULTILINE. - The regular comments are discarded, while MULTILINE - become doc strings, and are handled as expr statements. - regular consume everything from % or # up to but not - including \n:: - - (%|\#).* - - - Multiline COMMENT rule drops leading blanks, then eats everything - from ``%`` or ``#`` up to the end of line, `including` newline - character. COMMENT leaves one newline character, or else funny - things happen:: - - @TOKEN(r"(%|\#).*") - def t_COMMENT(t): - if not options.do_magic or t.value[-1] != "!": - t.lexer.lexpos = t.lexer.lexdata.find("\n",t.lexer.lexpos) - - Multiline comments:: +Mostly resolving ambiguities in Matlab definition. + +Reserved words +-------------- + +Reserved words are not reserved when used as fields. So +``return=1`` is illegal, but ``foo.return=1`` is fine. + +Round, square, and curly brackets +--------------------------------- + +In Python and in C it's simple -- function are called +using parentheses, and array subscripted with square +brackets. In Matlab and in Fortran both are done with +parentheses, so there is no visual difference between +function call ``foo(x)`` and indexing array ``foo``. + +Handling the white space +------------------------ + +There are four kinds of ws, each recognized by a dedicated rule: +NEWLINE, COMMENT, ELLIPSIS, and SPACES. Only NEWLINE returns a token, +the three others silently discard their input. NEWLINE collects +adjacent ``\n`` characters and returns a single SEMI token:: + + def t_NEWLINE(t): + r'\n+' + t.lexer.lineno += len(t.value) + if not t.lexer.parens and not t.lexer.braces: + t.value = ";" + t.type = "SEMI" + return t + +Comments come in two flavors -- regular and MULTILINE. +The regular comments are discarded, while MULTILINE +become doc strings, and are handled as expr statements. +regular consume everything from % or # up to but not +including \n:: + + (%|\#).* + +Multiline COMMENT rule drops leading blanks, then eats +everything from ``%`` or ``#`` up to the end of line, +`including` newline character. COMMENT leaves one +newline character, or else funny things happen:: + + @TOKEN(r"(%|\#).*") + def t_COMMENT(t): + if not options.do_magic or t.value[-1] != "!": + t.lexer.lexpos = t.lexer.lexdata.find("\n",t.lexer.lexpos) + +Multiline comments:: + + (^[ \t](%|\#).*\n)+ + +The pattern for multi-line comments works only if +re.MULTILINE flag is passed to ctor. + +Comments starting with ``%!`` have a special meaning TBD + +ELLIPSIS is the matlab way for line continuation. It +discards everything between ``...`` and the newline +character, including the trailing ``\n``:: + + def t_ELLIPSIS(t): + r"\.\.\..*\n" + t.lexer.lineno += 1 + +SPACES discards one or more horisontal space characters. +Not clear what escape sequence is supposed to do:: - (^[ \t](%|\#).*\n)+ + def t_SPACES(t): + r"(\\\n|[ \t\r])+" + +Disambiguating quotes +--------------------- - The pattern for multi-line comments works only if re.MULTILINE flag - is passed to ctor. +a. A quote, immediately following a reserved word is always a + ``STRING``. Implemented using inclusive state `afterkeyword`:: + + t.type = reserved.get(t.value,"IDENT") + if t.type != "IDENT" and t.lexer.lexdata[t.lexer.lexpos]=="'": + t.lexer.begin("afterkeyword") - Comments starting with ``%!`` have a special meaning TBD +b. A quote, immediately following any of: (1) an alphanumeric + charater, (2) right bracket, parenthesis or brace, or (3) + another ``TRANSPOSE``, is a ``TRANSPOSE``. Otherwise, it + starts a string. If the quote is separated from the term by + line continuation (...), matlab starts a string, so these + rules still hold:: - ELLIPSIS is the matlab way for line continuation. It discards - everything between ``...`` and the newline character, including - the trailing ``\n``:: + def t_TRANSPOSE(t): + r"(?<=\w|\]|\)|\})((\.')|')+" + # <---context ---><-quotes-> + # We let the parser figure out what that mix of quotes and + # dot-quotes, which is kept in t.value, really means. + return t - def t_ELLIPSIS(t): - r"\.\.\..*\n" - t.lexer.lineno += 1 - pass +Keyword ``end`` -- expression and statement +------------------------------------------- - SPACES discards one or more horisontal space characters. Not clear - what escape sequence is supposed to do:: +Any of: ``endwhile``, etc. are ``END_STMT``. Otherwise, +if ``end`` appears inside parentheses of any kind, +it's ``END_EXPR``. Otherwise, ``end`` is illegal. - def t_SPACES(t): - r"(\\\n|[ \t\r])+" - pass +Optional ``end`` statement as function terminator +------------------------------------------------- -#. ``p_TRANSPOSE`` - a. A quote, immediately following a reserved word is always a - ``STRING``. Implemented using inclusive state `afterkeyword`:: +Inconsistency between Matlab and Octave, solved +if the lexer effectively handles the whitespace:: - t.type = reserved.get(t.value,"IDENT") - if t.type != "IDENT" and t.lexer.lexdata[t.lexer.lexpos]=="'": - t.lexer.begin("afterkeyword") + function : FUNCTION + | END_STMT SEMI FUNCTION - b. A quote, immediately following any of: (1) an alphanumeric - charater, (2) right bracket, parenthesis or brace, or (3) - another ``TRANSPOSE``, is a ``TRANSPOSE``. Otherwise, it - starts a string. If the quote is separated from the term by - line continuation (...), matlab starts a string, so these - rules still holds.:: +This usage is consistent with the other cases -- (1) statements start +with a keyword and are terminated by the SEMI token, and (2) the +lexer combines several comments, blanks, and other junk as one +SEMI token. Compare parse.py rule for RETURN statement. - def t_TRANSPOSE(t): - r"(?<=\w|\]|\)|\})((\.')|')+" - # <---context ---><-quotes-> - # We let the parser figure out what that mix of quotes and - # dot-quotes, which is kept in t.value, really means. - return t +Semicolon as statement terminator, as column separator in matrices. +Comma, semicolon, and newline are statement terminators. In +matrix expressiions, whitespace is significant and separates elements +just as comma does. +Matrix state +------------ -#. Keyword ``end`` -- expression and statement - Any of: ``endwhile``, etc. are ``END_STMT``. Otherwise, if ``end`` - appears inside parentheses of any kind, it's ``END_EXPR``. - Otherwise, ``end`` is illegal. +In matrix state, consume whitespace separating two terms and +return a fake ``COMMA`` token. This allows parsing ``[1 2 3]`` as +if it was ``[1,2,3]``. Handle with care: ``[x + y]`` vs ``[x +y]`` -#. Optional ``end`` statement as function terminator - Inconsistency between Matlab and Octave, easily solved - if the lexer effectively handles the whitespace:: +Term T is:: - function : FUNCTION - | END_STMT SEMI FUNCTION + #. a name or a number + #. literal string enclosed in single or double quotes + #. (T) or [T] or {T} or T' or +T or -T - This usage is consistent with the other cases -- (1) statements start - with a keyword and are terminated by the SEMI token, and (2) the - lexer combines several comments, blanks, and other junk as one - SEMI token. Compare parse.py rule for RETURN statement. +Terms end with:: -#. Semicolon as statement terminator, as column separator in matrices. - Comma, semicolon, and newline are statement terminators. In - matrix expressiions, whitespace is significant and separates elements - just as comma does. + #. an alphanumeric charater \w + #. single quote (in octave also double-quote) + #. right parenthesis, bracket, or brace + #. a dot (after a number, such as 3. -#. Matrix state - In matrix state, consume whitespace separating two terms and - return a fake ``COMMA`` token. This allows parsing ``[1 2 3]`` as - if it was ``[1,2,3]``. Handle with care: ``[x + y]`` vs ``[x +y]`` +The pattern for whitespace accounts for ellipsis as a whitespace, and +for the trailing junk. - Term T is:: +Terms start with:: - (1) a name or a number - (2) literal string using single or doble quote - (3) (T) or [T] or {T} or T' or +T or -T + #. an alphanumeric character + #. a single or double quote, + #. left paren, bracket, or brace and finally + #. a dot before a digit, such as .3 . - Terms end with:: +TODO: what about curly brackets ??? +TODO: what about dot followed by a letter, as in field + + [foo .bar] + + t.lexer.lineno += t.value.count("\n") + t.type = "COMMA" + return t - (1) an alphanumeric charater \w - (2) single quote (in octave also double-quote) - (3) right parenthesis, bracket, or brace - (4) a dot (after a number, such as 3. +================ +Run-time support +================ - The pattern for whitespace accounts for ellipsis as a - whitespace, and for the trailing junk. +libsmop +------- - Terms start with:: +Shared library ``libsmop.so`` implements classes ``matlabarray``, + ``char``, and ``cellarray``, as well as some small functions:: - (1) an alphanumeric character - (2) a single or double quote, - (3) left paren, bracket, or brace and finally - (4) a dot before a digit, such as .3 . + def abs(a): return numpy.abs(a) - TODO: what about curly brackets ??? - TODO: what about dot followed by a letter, as in field - [foo .bar] - - t.lexer.lineno += t.value.count("\n") - t.type = "COMMA" - return t +Library ``libsmop.pyx`` is written in Cython, and is built as:: + + cython libsmop.pyx + gcc -Wno-cpp -I /usr/include/python2.7 -O2 -shared -o libsmop.so -fPIC libsmop.c +Once built, libsmop is imported:: -Class matlabarray -================= + from libsmop import * Matlab arrays differ from numpy arrays in many ways, and class -matlabarray captures the following differences: - -A. Base-one indexing - Following Fortran tradition, matlab starts array indexing with - one, not with zero. Correspondingly, the last element of a - N-element array is N, not N-1. - -#. C_CONTIGUOUS and F_CONTIGUOUS data layout - Matlab matrix elements are ordered in columns-first, aka - F_CONTIGUOUS order. By default, numpy arrays are C_CONTIGUOUS. - Instances of matlabarray are F_CONTIGUOUS, except if created - empty, in which case they are C_CONTIGUOUS. +``matlabarray`` captures these differences. There are two +natural places to call matlabarray. + +First, around numeric constants, (both scalars and arrays), +string and cellarray literals, and upon return from any function +-- either library or user defined. This looks terrible. + +Another possibility is to wrap the function arguments inside the +function + +Base-one indexing +----------------- + +Following FORTRAN tradition, Matlab starts array indexing with one, not +with zero. Correspondingly, the last element of a N-element array is N, +not N-1. + +C and FORTRAN data layout +------------------------- + +Matlab matrix elements are ordered in columns-first order, better known +as FORTRAN order. By default, numpy arrays use C layout. Instances of +``matlabarray`` use FORTRAN layout, except if created empty, in which +case they use C layout. - +-----------------------+--------------------------------------+ - | matlab | numpy | - +=======================+======================================+ - |:: |:: | - | | | - | > reshape(1:4,[2 2]) | >>> a=matlabarray([1,2,3,4]) | - | 1 3 | >>> a.reshape(2,2,order="F") | - | 2 4 | 1 3 | - | | 2 4 | - | | | - | | >>> a.reshape(2,2,order="C") | - | | 1 2 | - | | 3 4 | - +-----------------------+--------------------------------------+ - - >>> a=matlabarray([1,2,3,4]) - >>> a.flags.f_contiguous - True - >>> a.flags.c_contiguous - False - - >>> a=matlabarray() - >>> a.flags.c_contiguous - True - >>> a.flags.f_contiguous - False - -#. Auto-expanding arrays - Arrays are auto-expanded on out-of-bound assignment. Deprecated, - this feature is widely used in legacy code. In smop, out-of-bound - assignment is fully supported for row and column vectors, and for - their generalizations having shape ++-----------------------+--------------------------------------+ +| matlab | numpy | ++=======================+======================================+ +|:: |:: | +| | | +| > reshape(1:4,[2 2]) | >>> a=matlabarray([1,2,3,4]) | +| 1 3 | >>> reshape(a, [2,2]) | +| 2 4 | 1 3 | +| | 2 4 | ++-----------------------+--------------------------------------+ + +>>> a=matlabarray([1,2,3,4]) +>>> a.flags.f_contiguous +True +>>> a.flags.c_contiguous +False + +>>> a=matlabarray() +>>> a.flags.c_contiguous +True +>>> a.flags.f_contiguous +False + +Auto-expanding arrays +--------------------- + +Arrays are auto-expanded on out-of-bound assignment. Deprecated, +this feature is widely used in legacy code. In smop, out-of-bound +assignment is fully supported for row and column vectors, and for +their generalizations having shape - [1 1 ... N ... 1 1 1] - - These arrays may be resized along their only non-singular - dimension. For other arrays, new columns can be added to - F_CONTIGUOUS arrays, and new rows can be added to C_CONTIGUOUS - arrays. - - +----------------------------+----------------------------------+ - | matlab | numpy | - +============================+==================================+ - |:: |:: | - | | | - | > a=[] | >>> a=matlabarray() | - | > a(1)=123 | >>> a[1]=123 | - | > a | >>> a | - | 123 | 123 | - | | | - +----------------------------+----------------------------------+ - -#. Create by update - In matlab, arrays can be created by updating a non-existent array, - as in the following example: + [1 1 ... N ... 1 1 1] + +These arrays may be resized along their only non-singular dimension. +For other arrays, new columns can be added to F_CONTIGUOUS arrays, and +new rows can be added to C_CONTIGUOUS arrays. + ++----------------------------+----------------------------------+ +| matlab | numpy | ++============================+==================================+ +|:: |:: | +| | | +| > a=[] | >>> a=matlabarray() | +| > a(1)=123 | >>> a[1]=123 | +| > a | >>> a | +| 123 | 123 | +| | | ++----------------------------+----------------------------------+ + +Create by update +---------------- + +In Matlab, arrays can be created by updating a non-existent array, +as in the following example: >>> clear a >>> a(17) = 42 - This unique feature is not yet supported by smop, but can be - worked around by inserting assignments into the original matlab - code: +This unique feature is not yet supported by smop, but can be +worked around by inserting assignments into the original matlab +code: >>> a = [] >>> a(17) = 42 -#. Assignment as copy - Array data is not shared by copying or slice indexing. Instead - there is copy-on-write. +Assignment as copy +------------------ + +Array data is not shared by copying or slice indexing. Instead +there is copy-on-write. + +Everything is a matrix +---------------------- + +There are no zero or one-dimensional arrays. Scalars are +two-dimensional rather than zero-dimensional as in numpy. -#. Everything is a matrix - There are no zero or one-dimensional arrays. Scalars are - two-dimensional rather than zero-dimensional as in numpy. +Single subscript implies ravel +--------------------------------- + +TBD -#. Single subscript implies ravel. - TBD +Boadcasting rules are different +------------------------------- + +TBD -#. Broadcasting - Boadcasting rules are different +Boolean indexing +---------------- -#. Boolean indexing - TBD +TBD -#. Character string constants and escape sequences [ffd52d5fc5] - In Matlab, character strings are enclosed in single quotes, like - ``'this'``, and escape sequences are not recognized:: +Character string literals and escape sequences +----------------------------------------------- + +In Matlab, character strings are enclosed in single quotes, like +``'this'``, and escape sequences are not recognized:: matlab> size('hello\n') 1 7 - There are seven (!) characters in ``'hello\n'``, the last two being - the backslash and the letter ``n``. +There are seven (!) characters in ``'hello\n'``, the last two being +the backslash and the letter ``n``. - Two consecutive quotes are used to put a quote into a string:: +Two consecutive quotes are used to put a quote into a string:: matlab> 'hello''world' hello'world - In Octave, there are two kinds of strings: octave-style (enclosed - in double quotes), and matlab-style (enclosed in single quotes). - Octave-style strings do understand escape sequences:: +In Octave, there are two kinds of strings: octave-style (enclosed +in double quotes), and matlab-style (enclosed in single quotes). +Octave-style strings do understand escape sequences:: matlab> size("hello\n") 1 6 - There are six characters in ``"hello\n"``, the last one being - the newline character. +There are six characters in ``"hello\n"``, the last one being +the newline character. - Octave recognizes the same escape sequnces as C:: +Octave recognizes the same escape sequnces as C:: \" \a \b \f \r \t \0 \v \n \\ \nnn \xhh - where n is an octal digit and h is a hexadecimal digit. +where n is an octal digit and h is a hexadecimal digit. - Finally, two consecutive double-quote characters become a single - one, like here:: +Finally, two consecutive double-quote characters become a single +one, like here:: - octave> "hello""world" - hello"world + octave> "hello""world" + hello"world ---------------------------------------------------------------------- +=============== Data structures =============== -A. Empty vector [], empty string "", and empty cellarray {} - +----------------------------+----------------------------------+ - | matlab | numpy | - +============================+==================================+ - | :: | :: | - | | | - | > size([]) | >>> matlabarray().shape | - | 0 0 | (0, 0) | - | | | - | > size('') | >>> char().shape | - | 0 0 | (0, 0) | - | | | - | > size({}) | >>> cellarray().shape | - | 0 0 | (0, 0) | - +----------------------------+----------------------------------+ +Empty vector, empty string, and empty cellarray +----------------------------------------------- + ++----------------------------+----------------------------------+ +| matlab | numpy | ++============================+==================================+ +| :: | :: | +| | | +| > size([]) | >>> matlabarray().shape | +| 0 0 | (0, 0) | +| | | +| > size('') | >>> char().shape | +| 0 0 | (0, 0) | +| | | +| > size({}) | >>> cellarray().shape | +| 0 0 | (0, 0) | ++----------------------------+----------------------------------+ + - -#. Scalars are 1x1 matrices - +----------------------------+----------------------------------+ - | matlab | numpy | - +============================+==================================+ - | :: | :: | - | | | - | > a=17 | >>> a=matlabarray(17) | - | > size(a) | >>> a.shape | - | 1 1 | 1 1 | - | | | - +----------------------------+----------------------------------+ - -#. Rectangular char arrays - Class char inherits from class matlabarray the usual matlab array - behaviour -- base-1 indexing, Fortran data order, auto-expand on - out-of-bound assignment, etc. - - +----------------------------+----------------------------------+ - | matlab | numpy | - +============================+==================================+ - | :: | :: | - | | | - | > s='helloworld' | >>> s=char('helloworld') | - | > size(s) | >>> print size_(s) | - | 1 10 | (1,10) | - | > s(1:5)='HELLO' | >>> s[1:5]='HELLO' | - | > s | >>> print s | - | HELLOworld | HELLOworld | - | > resize(s,[2 5]) | >>> print resize_(s,[2,5]) | - | HELLO | HELLO | - | world | world | - +----------------------------+----------------------------------+ - -#. Row vector - +----------------------------+----------------------------------+ - | matlab | numpy | - +============================+==================================+ - | :: | :: | - | | | - | > s=[1 2 3] | >>> s=matlabarray([1,2,3]) | - | | | - +----------------------------+----------------------------------+ - - -#. Column vector - +----------------------------+----------------------------------+ - | matlab | numpy | - +============================+==================================+ - |:: |:: | - | | | - | > a=[1;2;3] | >>> a=matlabarray([[1], | - | | [2], | - | | [2]]) | - | > size(a) | >>> a.shape | - | 3 1 | (3, 1) | - +----------------------------+----------------------------------+ - - -#. Cell arrays - Cell arrays subclass matlabarray and inherit the usual matlab - array behaviour -- base-1 indexing, Fortran data order, expand on - out-of-bound assignment, etc. Unlike matlabarray, each element of - cellarray holds a python object. - - +----------------------------+----------------------------------+ - | matlab | numpy | - +============================+==================================+ - |:: |:: | - | | | - | > a = { 'abc', 123 } | >>> a=cellarray(['abc',123]) | - | > a{1} | >>> a[1] | - | abc | abc | - +----------------------------+----------------------------------+ - -#. Cell arrays of strings - In matlab, cellstrings are cell arrays, where each cell contains a - char object. In numpy, class cellstring derives from matlabarray, - and each cell contains a native python string (not a char - instance). - - +----------------------------+----------------------------------+ - | matlab | numpy | - +============================+==================================+ - |:: |:: | - | | | - | > a = { 'abc', 'hello' } | >>> a=cellstring(['abc', | - | | 'hello']) | - | > a{1} | >>> a[1] | - | abc | abc | - +----------------------------+----------------------------------+ - ----------------------------------------------------------------------- - -Data structures - All matlab data structures subclass from matlabarray - -Structs - TBD - -Function pointers - Handles @ +Scalars are 1x1 matrices +------------------------ + ++----------------------------+----------------------------------+ +| matlab | numpy | ++============================+==================================+ +| :: | :: | +| | | +| > a=17 | >>> a=matlabarray(17) | +| > size(a) | >>> a.shape | +| 1 1 | 1 1 | +| | | ++----------------------------+----------------------------------+ + +Character string literals +------------------------- + +Matlab strings inherit their behavior from Matlab numeric arrays. This +includes base-1 indexing, Fortran data order, and some unexpected +features, such as auto-expand on out of bound assignment (Matlab strings +are mutable objects). Unless we know better, Matlab string literals +should be translated to instances of class ``char``, which inherits from +``matlabarray``. + ++----------------------------+----------------------------------+ +| matlab | numpy | ++============================+==================================+ +| :: | :: | +| | | +| > s='helloworld' | >>> s=char('helloworld') | +| > size(s) | >>> print size_(s) | +| 1 10 | (1,10) | +| > s(1:5)='HELLO' | >>> s[1:5]=char('HELLO') | +| > s | >>> print s | +| HELLOworld | HELLOworld | +| > resize(s,[2 5]) | >>> print resize_(s,[2,5]) | +| HELLO | HELLO | +| world | world | ++----------------------------+----------------------------------+ + +Row vectors +----------- + +Rows are matrices whose size is [1 N]. When concatenated, rows are +joined along the first dimension, so concatenating two row vectors +of length M and N yields a row vector of length M+N. + ++----------------------------+----------------------------------+ +| matlab | numpy | ++============================+==================================+ +| :: | :: | +| | | +| > s=[1 2 3] | >>> s=matlabarray([1,2,3]) | +| > t=[4 5 6] | >>> t=matlabarray([4,5,6]) | +| > u=[s t] | >>> print concat([s,t]) | +| | 1 2 3 4 5 6 | ++----------------------------+----------------------------------+ String concatenation - Array concatenation not implemented +-------------------- - >>> ['hello' 'world'] - helloworld +String concatenation is consistent with row vectors concatenation +because string literals are row vectors + ++----------------------------+----------------------------------+ +| matlab | numpy | ++============================+==================================+ +| :: | :: | +| | | +| > s='abc' | >>> s = char('abc') | +| > t='ABC' | >>> t = char('ABC') | +| > [s t] | >>> print concat([s,t]) | +| abcABC | 1 2 3 4 5 6 | ++----------------------------+----------------------------------+ + +Column vector +------------- + ++----------------------------+----------------------------------+ +| matlab | numpy | ++============================+==================================+ +|:: |:: | +| | | +| > a=[1;2;3] | >>> a=matlabarray([[1], | +| | [2], | +| | [2]]) | +| > size(a) | >>> a.shape | +| 3 1 | (3, 1) | ++----------------------------+----------------------------------+ + +Cell arrays +----------- + +Cell arrays subclass matlabarray and inherit the usual matlab +array behaviour -- base-1 indexing, Fortran data order, expand on +out-of-bound assignment, etc. Unlike matlabarray, each element of +cellarray holds a python object. + ++----------------------------+----------------------------------+ +| matlab | numpy | ++============================+==================================+ +|:: |:: | +| | | +| > a = { 'abc', 123 } | >>> a=cellarray(['abc',123]) | +| > a{1} | >>> a[1] | +| abc | abc | ++----------------------------+----------------------------------+ + +Cell arrays of strings +---------------------- + +In matlab, cellstrings are cell arrays, where each cell contains a +char object. In numpy, class cellstring derives from matlabarray, +and each cell contains a native python string (not a char +instance). + ++----------------------------+----------------------------------+ +| matlab | numpy | ++============================+==================================+ +|:: |:: | +| | | +| > a = { 'abc', 'hello' } | >>> a=cellstring(['abc', | +| | 'hello']) | +| > a{1} | >>> a[1] | +| abc | abc | ++----------------------------+----------------------------------+ + +---------------------------------------------------------------------- .. vim: tw=70:sw=2 diff --git a/setup.py b/setup.py index 58518a0e..7ce84be3 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,7 @@ import os from setuptools import setup -try: - __VERSION__ = os.popen("git describe --tags", "r").read().strip() -except: - __VERSION__ = "0.34" - -open("smop/version.py","w").write("__version__='%s'\n" % __VERSION__) +from smop.version import __version__ as __VERSION__ setup( author = 'Victor Leikehman', diff --git a/smop/Makefile b/smop/Makefile index c9ca375c..0a2c70ea 100644 --- a/smop/Makefile +++ b/smop/Makefile @@ -1,34 +1,36 @@ -# for py3: make PYTHON=python3 CYTHON="cython -3" V=3.4 +# for py3: make PYTHON=python3 CYTHON="cython -3" V=3 -OCTAVE = /home/lei/octave-4.0.2 -SCRIPTS = $(OCTAVE)/scripts +OCTAVE = /home/lei/smop/smop/octave-4.2.1 +SCRIPTS = $(OCTAVE)/scripts/specfun -CYTHON = cython -PYTHON = python -XFILES = -x inputParser +COVERAGE = python3 -m coverage -#m,uiputfile.m,uigetfile.m,qmr.m +CYTHON = cython +PYTHON = python$V +XFILES = -x inputParser.m,dblquad.m,triplequad.m -#quadgk.m,quadl.m,triplequad.m,dblquad.m -#installed_packages.m,stft.m,legend.m,__plt_get_axis_arg__.m,rotate.m,print.m,__ghostscript__.m,__gnuplot_print__.m,set.m,edit.m,what.m,usejava.m,javachk.m,__go_draw_axes__.m,interp3.m,interp2.m,interpn.m,randi.m,interp1.m,spdiags.m,importdata.m,stemleaf.m +SRCSMOP = main.py parse.py backend.py resolve.py options.py FLAGS = MYFLAGS= -V = 2.7 +#V = 2.7 +V = 3 all: - make -B FLAGS= liboctave.py | wc + make -B FLAGS= liboctave.py + $(COVERAGE) run -p main.py solver.m + $(COVERAGE) combine foo: - make -B FLAGS=-C liboctave.py | wc - make -B FLAGS=-N liboctave.py | wc - make -B FLAGS=-T liboctave.py | wc - make -B FLAGS=-CN liboctave.py | wc - make -B FLAGS=-TN liboctave.py | wc - make -B FLAGS=-CT liboctave.py | wc - make -B FLAGS=-CTN liboctave.py | wc + make -B FLAGS=-C liboctave.py + make -B FLAGS=-N liboctave.py + make -B FLAGS=-T liboctave.py + make -B FLAGS=-CN liboctave.py + make -B FLAGS=-TN liboctave.py + make -B FLAGS=-CT liboctave.py + make -B FLAGS=-CTN liboctave.py liboctave.py: - find $(SCRIPTS) -name \*.m | xargs $(PYTHON) main.py -o $@ $(MYFLAGS) $(FLAGS) $(XFILES) $^ + find $(SCRIPTS) -name \*.m | xargs $(PYTHON) main.py --verbose -o $@ $(MYFLAGS) $(FLAGS) $(XFILES) $^ #$(PYTHON) $@ clean: @@ -44,7 +46,7 @@ regress: %.py: %.m $(PYTHON) main.py -o $@ $^ - $(PYTHON) $@ + #$(PYTHON) $@ %.pdf: %.dot dot -Tpdf -o $@ $^ diff --git a/smop/backend.py b/smop/backend.py index 513684c7..23005b15 100644 --- a/smop/backend.py +++ b/smop/backend.py @@ -322,7 +322,7 @@ def _backend(self,level=0): return " + ".join(a._backend() for a in self.args) else: #import pdb; pdb.set_trace() - return "cat(%s)" % self.args[0]._backend() + return "concat([%s])" % self.args[0]._backend() @extend(node.null_stmt) def _backend(self,level=0): @@ -364,7 +364,10 @@ def _backend(self,level=0): @extend(node.string) def _backend(self,level=0): - return "'%s'" % str(self.value).encode("string_escape") + try: + return "'%s'" % str(self.value).encode("string_escape") + except: + return "'%s'" % str(self.value) @extend(node.sub) def _backend(self,level=0): diff --git a/smop/core.py b/smop/core.py deleted file mode 100644 index 7da2c9b6..00000000 --- a/smop/core.py +++ /dev/null @@ -1,732 +0,0 @@ -# SMOP compiler runtime support library -# Copyright 2014 Victor Leikehman - -# MIT license - -import __builtin__ - -import numpy -from numpy import sqrt,prod,exp,log,dot,multiply,inf -from numpy.fft import fft2 -from numpy.linalg import inv -from numpy.linalg import qr as _qr -try: - from scipy.linalg import schur as _schur -except ImportError: - pass -import numpy as np - -import os,sys,copy,time -from sys import stdin,stdout,stderr -try: - from scipy.io import loadmat -except: - pass -import unittest -from scipy.special import gamma -from numpy import rint as fix - -def isvector_or_scalar(a): - """ - one-dimensional arrays having shape [N], - row and column matrices having shape [1 N] and - [N 1] correspondingly, and their generalizations - having shape [1 1 ... N ... 1 1 1]. - Scalars have shape [1 1 ... 1]. - Empty arrays dont count - """ - try: - return a.size and a.ndim-a.shape.count(1) <= 1 - except: - return False -def isvector(a): - """ - one-dimensional arrays having shape [N], - row and column matrices having shape [1 N] and - [N 1] correspondingly, and their generalizations - having shape [1 1 ... N ... 1 1 1] - """ - try: - return a.ndim-a.shape.count(1) == 1 - except: - return False - -class matlabarray(np.ndarray): - """ - >>> matlabarray() - matlabarray([], shape=(0, 0), dtype=float64) - >>> matlabarray([arange(1,5), arange(1,5)]) - matlabarray([1, 2, 3, 4, 5, 1, 2, 3, 4, 5]) - >>> matlabarray(["hello","world"]) - matlabarray("helloworld") - """ - - def __new__(cls,a=[],dtype=None): - obj = np.array(a, - dtype=dtype, - copy=False, - order="F", - ndmin=2).view(cls).copy(order="F") - if obj.size == 0: - obj.shape = (0,0) - return obj - - #def __array_finalize__(self,obj): - - def __copy__(self): - return np.ndarray.copy(self,order="F") - - def __iter__(self): - """ must define iter or char won't work""" - return np.asarray(self).__iter__() - - def compute_indices(self,index): - if not isinstance(index,tuple): - index = index, - if len(index) != 1 and len(index) != self.ndim: - raise IndexError - indices = [] - for i,ix in enumerate(index): - if ix.__class__ is end: - indices.append(self.shape[i]-1+ix.n) - elif ix.__class__ is slice: - if self.size == 0 and ix.stop is None: - raise IndexError - if len(index) == 1: - n = self.size - else: - n = self.shape[i] - indices.append(np.arange((ix.start or 1)-1, - ix.stop or n, - ix.step or 1, - dtype=int)) - else: - try: - indices.append(int(ix)-1) - except: - indices.append(np.asarray(ix).astype("int32")-1) - if len(indices) == 2 and isvector(indices[0]) and isvector(indices[1]): - indices[0].shape = (-1,1) - indices[1].shape = (-1,) - return tuple(indices) - - def __getslice__(self,i,j): - if i == 0 and j == sys.maxsize: - return self.reshape(-1,1,order="F") - return self.__getitem__(slice(i,j)) - - def __getitem__(self,index): - return matlabarray(self.get(index)) - - def get(self,index): - #import pdb; pdb.set_trace() - indices = self.compute_indices(index) - if len(indices) == 1: - return np.ndarray.__getitem__(self.reshape(-1,order="F"),indices) - else: - return np.ndarray.__getitem__(self,indices) - - def __setslice__(self,i,j,value): - if i == 0 and j == sys.maxsize: - index = slice(None,None) - else: - index = slice(i,j) - self.__setitem__(index,value) - - def sizeof(self,ix): - if isinstance(ix,int): - n = ix+1 - elif isinstance(ix,slice): - n = ix.stop - elif isinstance(ix,(list,np.ndarray)): - n = max(ix)+1 - else: - assert 0,ix - if not isinstance(n,int): - raise IndexError - return n - - def __setitem__(self,index,value): - #import pdb; pdb.set_trace() - indices = self.compute_indices(index) - try: - if len(indices) == 1: - np.asarray(self).reshape(-1,order="F").__setitem__(indices,value) - else: - np.asarray(self).__setitem__(indices,value) - except (ValueError,IndexError): - #import pdb; pdb.set_trace() - if not self.size: - new_shape = [self.sizeof(s) for s in indices] - self.resize(new_shape,refcheck=0) - np.asarray(self).__setitem__(indices,value) - elif len(indices) == 1: - # One-dimensional resize is only implemented for - # two cases: - # - # a. empty matrices having shape [0 0]. These - # matries may be resized to any shape. A[B]=C - # where A=[], and B is specific -- A[1:10]=C - # rather than A[:]=C or A[1:end]=C - if self.size and not isvector_or_scalar(self): - raise IndexError("One-dimensional resize " - "works only on vectors, and " - "row and column matrices") - # One dimensional resize of scalars creates row matrices - # ai = 3 - # a(4) = 1 - # 3 0 0 1 - n = self.sizeof(indices[0]) # zero-based - if max(self.shape) == 1: - new_shape = list(self.shape) - new_shape[-1] = n - else: - new_shape = [(1 if s==1 else n) for s in self.shape] - self.resize(new_shape,refcheck=0) - np.asarray(self).reshape(-1,order="F").__setitem__(indices,value) - else: - new_shape = list(self.shape) - if self.flags["C_CONTIGUOUS"]: - new_shape[0] = self.sizeof(indices[0]) - elif self.flags["F_CONTIGUOUS"]: - new_shape[-1] = self.sizeof(indices[-1]) - self.resize(new_shape,refcheck=0) - np.asarray(self).__setitem__(indices,value) - - def __repr__(self): - return self.__class__.__name__ + repr(np.asarray(self))[5:] - - def __str__(self): - return str(np.asarray(self)) - - def __add__(self,other): - return matlabarray(np.asarray(self)+np.asarray(other)) - - def __neg__(self): - return matlabarray(np.asarray(self).__neg__()) - -class end(object): - def __add__(self,n): - self.n = n - return self - def __sub__(self,n): - self.n = -n - return self -#### -class cellarray(matlabarray): - """ - Cell array corresponds to matlab ``{}`` - - - """ - - def __new__(cls, a=[]): - """ - Create a cell array and initialize it with a. - Without arguments, create an empty cell array. - - Parameters: - a : list, ndarray, matlabarray, etc. - - >>> a=cellarray([123,"hello"]) - >>> print a.shape - (1, 2) - - >>> print a[1] - 123 - - >>> print a[2] - hello - """ - obj = np.array(a, - dtype=object, - order="F", - ndmin=2).view(cls).copy(order="F") - if obj.size == 0: - obj.shape = (0,0) - return obj - - def __getitem__(self,index): - return self.get(index) - -# def __str__(self): -# if self.ndim == 0: -# return "" -# if self.ndim == 1: -# return "".join(s for s in self) -# if self.ndim == 2: -# return "\n".join("".join(s) for s in self) -# raise NotImplementedError - - -class cellstr(matlabarray): - """ - >>> s=cellstr(char('helloworldkitty').reshape(3,5)) - >>> s - cellstr([['hello', 'world', 'kitty']], dtype=object) - >>> print s - hello - world - kitty - >>> s.shape - (1, 3) - """ - - def __new__(cls, a): - """ - Given a two-dimensional char object, - create a cell array where each cell contains - a line. - """ - obj = np.array(["".join(s) for s in a], - dtype=object, - copy=False, - order="C", - ndmin=2).view(cls).copy(order="F") - if obj.size == 0: - obj.shape = (0,0) - return obj - - def __str__(self): - return "\n".join("".join(s) for s in self.reshape(-1)) - - def __getitem__(self,index): - return self.get(index) - - -class char(matlabarray): - """ - class char is a rectangular string matrix, which - inherits from matlabarray all its features except - dtype. - - >>> s=char() - >>> s.shape - (0, 0) - - >>> s=char('helloworld').reshape(2,5) - >>> print s - hello - world - >>> s=char([104, 101, 108, 108, 111, 119, 111, 114, 108, 100]) - >>> s.shape = 2,5 - >>> print s - hello - world - """ - - def __new__(cls, a=""): - if not isinstance(a,str): - a = "".join([chr(c) for c in a]) - obj = np.array(list(a), - dtype='|S1', - copy=False, - order="F", - ndmin=2).view(cls).copy(order="F") - if obj.size == 0: - obj.shape = (0,0) - return obj - - def __getitem__(self,index): - return self.get(index) - - def __str__(self): - if self.ndim == 0: - return "" - if self.ndim == 1: - return "".join(s for s in self) - if self.ndim == 2: - return "\n".join("".join(s) for s in self) - raise NotImplementedError - -class struct(object): - def __init__(self,*args): - for i in range(0,len(args),2): - setattr(self,str(args[i]),args[i+1]) - -NA = numpy.NaN - -def abs(a): - return numpy.abs(a) - -def all(a): - return numpy.all(a) - -def any(a): - return numpy.any(a) - -def arange(start,stop,step=1,**kwargs): - """ - >>> a=arange(1,10) # 1:10 - >>> size(a) - matlabarray([[ 1, 10]]) - """ - expand_value = 1 if step > 0 else -1 - return matlabarray(np.arange(start, - stop+expand_value, - step, - **kwargs).reshape(1,-1),**kwargs) -def cat(*args): - return matlabarray(np.concatenate([matlabarray(a) for a in args],axis=1)).reshape(-1) - -def ceil(a): - return numpy.ceil(a) - -def cell(*args): - if len(args) == 1: - args += args - return cellarray(np.zeros(args,dtype=object,order="F")) - -def clc(): - pass - -def copy(a): - return matlabarray(np.asanyarray(a).copy(order="F")) - -def deal(a,**kwargs): - #import pdb; pdb.set_trace() - return tuple([ai for ai in a.flat]) - -def disp(*args): - print (args) - -def eig(a): - u,v = np.linalg.eig(a) - return u.T - -def logical_not(a): - return numpy.logical_not(a) - -def logical_and(a,b): - return numpy.logical_and(a,b) - -def logical_or(a,b): - return numpy.logical_or(a,b) - -def exist(a,b): - if str(b) == 'builtin': - return str(a) in globals() - if str(b) == 'file': - return os.path.exists(str(a)) - raise NotImplementedError - -def false(*args): - if not args: - return False # or matlabarray(False) ??? - if len(args) == 1: - args += args - return np.zeros(args,dtype=bool,order="F") - -def find(a,n=None,d=None,nargout=1): - if d: - raise NotImplementedError - - # there is no promise that nonzero or flatnonzero - # use or will use indexing of the argument without - # converting it to array first. So we use asarray - # instead of asanyarray - if nargout == 1: - i = np.flatnonzero(np.asarray(a)).reshape(1,-1)+1 - if n is not None: - i = i.take(n) - return matlabarray(i) - if nargout == 2: - i,j = np.nonzero(np.asarray(a)) - if n is not None: - i = i.take(n) - j = j.take(n) - return (matlabarray((i+1).reshape(-1,1)), - matlabarray((j+1).reshape(-1,1))) - raise NotImplementedError - -def floor(a): - return numpy.floor(a) - -def fopen(*args): - try: - fp = open(*args) - assert fp != -1 - return fp - except: - return -1 - -def fflush(fp): - fp.flush() - -def fprintf(fp,fmt,*args): - if not isinstance(fp,file): - fp = stdout - fp.write(str(fmt) % args) - -def fullfile(*args): - return os.path.join(*args) - -# implemented in "scripts/set/intersect.m" -#def intersect(a,b,nargout=1): -# if nargout == 1: -# c = sorted(set(a) & set(b)) -# if isinstance(a,str): -# return "".join(c) -# elif isinstance(a,list): -# return c -# else: -# # FIXME: the result is a column vector if -# # both args are column vectors; otherwise row vector -# return np.array(c) -# raise NotImplementedError -# -def iscellstr(a): - # TODO return isinstance(a,cellarray) and all(ischar(t) for t in a.flat) - return isinstance(a,cellarray) and all(isinstance(t,str) for t in a.flat) - -def ischar(a): - try: - return a.dtype == "|S1" - except AttributeError: - return False -# ---------------------------------------------------- -def isempty(a): - try: - return 0 in np.asarray(a).shape - except AttributeError: - return False - -def isequal(a,b): - return np.array_equal(np.asanyarray(a), - np.asanyarray(b)) - -def isfield(a,b): - return str(b) in a.__dict__.keys() - -def ismatrix(a): - return True - -def isnumeric(a): - return np.asarray(a).dtype in (int,float) - -def isscalar(a): - """np.isscalar returns True if a.__class__ is a scalar - type (i.e., int, and also immutable containers str and - tuple, but not list.) Our requirements are different""" - try: - return a.size == 1 - except AttributeError: - return np.isscalar(a) - -def length(a): - try: - return __builtin__.max(np.asarray(a).shape) - except ValueError: - return 1 - -try: - def load(a): - return loadmat(a) # FIXME -except: - pass - -def max(a, d=0, nargout=0): - if d or nargout: - raise NotImplementedError - return np.amax(a) - -def min(a, d=0, nargout=0): - if d or nargout: - raise NotImplementedError - return np.amin(a) - -def mod(a,b): - try: - return a % b - except ZeroDivisionError: - return a - -def ndims(a): - return np.asarray(a).ndim - -def numel(a): - return np.asarray(a).size - -def ones(*args,**kwargs): - if not args: - return 1.0 - if len(args) == 1: - args += args - return matlabarray(np.ones(args,order="F",**kwargs)) - -#def primes2(upto): -# primes=np.arange(2,upto+1) -# isprime=np.ones(upto-1,dtype=bool) -# for factor in primes[:int(math.sqrt(upto))]: -# if isprime[factor-2]: isprime[factor*2-2::factor]=0 -# return primes[isprime] -# -#def primes(*args): -# return _primes.primes(*args) - -def qr(a): - return matlabarray(_qr(np.asarray(a))) - -def rand(*args,**kwargs): - if not args: - return np.random.rand() - if len(args) == 1: - args += args - try: - return np.random.rand(np.prod(args)).reshape(args,order="F") - except: - pass - -def assert_(a,b=None,c=None): - if c: - if c >= 0: - assert (abs(a-b) < c).all() - else: - assert (abs(a-b) < abs(b*c)).all() - elif b is None: - assert a - else: - #assert isequal(a,b),(a,b) - #assert not any(a-b == 0) - assert (a==b).all() - -def shared(a): - pass - -def rand(*args,**kwargs): - """from core aka libsmop.py""" - return np.random.rand() - # if not args: - # return np.random.rand() - # if len(args) == 1: - # args += args - # try: - # return np.random.rand(np.prod(args)).reshape(args,order="F") - # except: - # pass - -def randn(*args,**kwargs): - if not args: - return np.random.randn() - if len(args) == 1: - args += args - try: - return np.random.randn(np.prod(args)).reshape(args,order="F") - except: - pass - -def ravel(a): - return np.asanyarray(a).reshape(-1,1) - -def roots(a): - - return matlabarray(np.roots(np.asarray(a).ravel())) - -def round(a): - return np.round(np.asanyarray(a)) - -def rows(a): - return np.asarray(a).shape[0] - -def schur(a): - return matlabarray(_schur(np.asarray(a))) - -def size(a, b=0, nargout=1): - """ - >>> size(zeros(3,3)) + 1 - matlabarray([[4, 4]]) - """ - s = np.asarray(a).shape - if s is (): - return 1 if b else (1,)*nargout - # a is not a scalar - try: - if b: - return s[b-1] - else: - return matlabarray(s) if nargout <= 1 else s - except IndexError: - return 1 - -def size_equal(a,b): - if a.size != b.size: - return False - for i in range(len(a.shape)): - if a.shape[i] != b.shape[i]: - return False - return True - -from numpy import sqrt -sort = __builtin__.sorted - -def strcmp(a,b): - return str(a) == str(b) - -def strread(s, format="", nargout=1): - if format == "": - a = [float(x) for x in s.split()] - return tuple(a) if nargout > 1 else np.asanyarray([a]) - raise NotImplementedError - -def strrep(a,b,c): - return str(a).replace(str(b),str(c)) - -def sum(a, dim=None): - if dim is None: - return np.asanyarray(a).sum() - else: - return np.asanyarray(a).sum(dim-1) - -def toupper(a): - return char(str(a.data).upper()) - -true = True - -def tic(): - return time.clock() - -def toc(t): - return time.clock()-t - -def true(*args): - if len(args) == 1: - args += args - return matlabarray(np.ones(args,dtype=bool,order="F")) - -def version(): - return char('0.29') - -def zeros(*args,**kwargs): - if not args: - return 0.0 - if len(args) == 1: - args += args - return matlabarray(np.zeros(args,**kwargs)) - -def isa(a,b): - return True - -def print_usage(): - raise Exception - -def function(f): - def helper(*args,**kwargs): - helper.nargin = len(args) - helper.varargin = cellarray(args) - return f(*args,**kwargs) - return helper - -def error(s): - raise s - -def isreal(a): - return True - -eps = np.finfo(float).eps -#print(np.finfo(np.float32).eps) - -if __name__ == "__main__": - import doctest - doctest.testmod() - -# vim:et:sw=4:si:tw=60 diff --git a/smop/lexer.py b/smop/lexer.py index 8620c9af..1b863d9d 100644 --- a/smop/lexer.py +++ b/smop/lexer.py @@ -98,7 +98,10 @@ def unescape(s): if s[0] == "'": return s[1:-1].replace("''", "'") else: - return s[1:-1].decode("string_escape") + try: + return s[1:-1].decode("string_escape") + except: + return s[1:-1] @TOKEN(mos) def t_afterkeyword_STRING(t): @@ -339,11 +342,11 @@ def main(): while 1: try: line += raw_input("=>> ").decode("string_escape") - print len(line), [c for c in line] + print(len(line), [c for c in line]) except EOFError: reload(sys.modules["lexer.py"]) lexer.input(line) - print list(tok for tok in lexer) + print(list(tok for tok in lexer)) line = "" @@ -354,4 +357,4 @@ def main(): buf = open(sys.argv[1]).read() lexer.input(buf) for tok in lexer: - print tok + print(tok) diff --git a/smop/libscripts/.gitignore b/smop/libscripts/.gitignore deleted file mode 100644 index f104652b..00000000 --- a/smop/libscripts/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.py diff --git a/smop/libscripts/Makefile b/smop/libscripts/Makefile deleted file mode 100644 index bdebbb11..00000000 --- a/smop/libscripts/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -# for py3: make PYTHON=python3 CYTHON="cython -3" V=3.4 -VPATH = $(shell find $(SCRIPTS) -type d) -OCTAVE = /home/lei/octave-4.0.2 -SCRIPTS= $(OCTAVE)/scripts -PYFILES= $(sort $(subst .m,.py,$(notdir $(MFILES) ))) -MFILES = $(shell find $(SCRIPTS) -name \*.m -type f) -CYTHON = cython -PYTHON = python -CAT = cat -XFILES = -x fnmatch.m -FLAGS = -v -SMOP = $(PYTHON) ../main.py - -V = 2.7 - -all: - $(SMOP) $(FLAGS) $(XFILES) $(MFILES) - rm -f libscripts.py - $(CAT) *.py > libscripts.py - -regress: - $(SMOP) $(FLAGS) $(MFILES) | grep Error | wc - -.PHONY: all pyfiles clean - -clean: - rm -f libscripts.py *.pyc *.py - -%.py: %.m - $(SMOP) $^ $(FLAGS) $(XFILES) - -%.pyc: %.py - $(PYTHON) -m py_compile $@ - -.DELETE_ON_ERROR: - diff --git a/smop/m.pl b/smop/m.pl deleted file mode 100644 index 8e63299f..00000000 --- a/smop/m.pl +++ /dev/null @@ -1,132 +0,0 @@ -:- dynamic is_definition/1. -:- dynamic is_reference/1. -:- dynamic resolve/2. -:- dynamic do_resolve/2. -:- dynamic lhs_resolve/2. - -:- op(800,xfy, (=.)). - -prog([ - solver =. matlab_function(ai,af,w), - nBlocks =. matlab_max(matlab_ravel(ai)), - [m,n] =. matlab_size(ai), - ii =. [0, 1, 0,-1], - jj =. [1, 0,-1, 0], - a =. ai, - mv =. []]). - -% b-code down stack grows right -% + + f | | -% 2 + 2 f | x | -% 3 + 2 3 f | x | y -% [] + [2,3] f | [x,y] | -% () 5 f(x,y) | | - -% ?- do_resolve(a=b). -% = -% a -% b -% [] -% () - -% () apply/2 -% [] list/_ -% =/2 -% TODO -% 0. Copy state of is_def/is_ref --> resolve statements -% 4, const rank shape -% 6. SSA -% 8. Macroexpand -% 10. parser -% 12. backend - -name_ident(Name,Ident) :- - % Given a Name, create ident(Name,Addr) - % having brand new Addr. - gensym('',Atom), - atom_number(Atom,Addr), - Ident=ident(Name,Addr). - -cleanupall :- - retractall(is_reference(Ident)), - retractall(is_definition(Ident)). - -do_resolve(X,Y) :- - resolve(X,Y). - %findall(Ident, is_arrayref(Ident), IdentList). - -is_arrayref(ident(Name,Addr)) :- - is_reference(ident(Name,Addr)), - is_definition(ident(Name,_)). - -is_callsite(ident(Name,Addr)) :- - is_reference(ident(Name,Addr)), - \+ is_definition(ident(Name,_)). - -resolve(Name,Name) :- - atom(Name), - !, - name_ident(Name,Ident), - assertz(is_reference(Ident)). - -resolve(A,A) :- - number(A), - !. - -resolve(A=.B, C=.D) :- - !, - resolve(B,D), - lhs_resolve(A,C). - %lhs_resolve(A,A). - -resolve([], []) :- - !. - -resolve([A|B], [A|B]) :- - !, - resolve(A,A), - resolve(B,B). - -resolve(A,A) :- - compound(A), - !, - compound_name_arguments(A,B,C), - resolve(B,B), - resolve(C,C). - -lhs_resolve(Name,Name) :- % A=... - atom(Name), - !, - name_ident(Name,Ident), - assertz(is_definition(Ident)). - -lhs_resolve(A,A) :- - number(A), - !. - -%lhs_resolve(A=.B, A=.B) :- % A=B... -% !, -% resolve(B,B), -% lhs_resolve(A,A). - -lhs_resolve([], []) :- - !. - -lhs_resolve([A|B], [A|B]) :- - !, - lhs_resolve(A,A), - lhs_resolve(B,B). - -lhs_resolve(A,A) :- % A(B)= ... - compound(A), - !, - compound_name_arguments(A,B,C), - lhs_resolve(B,B), - resolve(C,C). - -has_definitions(Name,AddrList) :- - findall(Addr, is_definition(ident(Name,Addr)), AddrList). - -rank(matlab_size(_),1). - -% vim : syntax=prolog diff --git a/smop/main.py b/smop/main.py index 13d29a17..899489d2 100644 --- a/smop/main.py +++ b/smop/main.py @@ -1,6 +1,8 @@ # SMOP -- Simple Matlab/Octave to Python compiler # Copyright 2011-2016 Victor Leikehman +from __future__ import print_function + import py_compile import tempfile import fnmatch @@ -21,35 +23,18 @@ def print_header(fp): if options.no_header: return - print >> fp, "# Autogenerated with SMOP " + version.__version__ - # print >> fp, "from __future__ import division" - print >> fp, "from smop.core import *" - # if options.link: - # print >> fp, "from %s import *" % options.link - print >> fp, "#", options.filename - + print("# Autogenerated with SMOP ", + version.__version__, file=fp) + print("from libsmop import *", file=fp) + print("#", options.filename, file=fp) def main(): - tar = None if "M" in options.debug: import pdb pdb.set_trace() if not options.filelist: - if options.archive: - tar = tarfile.open(options.archive) - options.filelist = tar.getnames() - elif options.code: - tmp = tempfile.NamedTemporaryFile(suffix=".m") - tmp.file.write(options.code) - tmp.file.flush() - options.filelist = [tmp.name] - if options.output: - print "Conflicting options -c and -o" - return - options.output = "-" - else: - options.parser.print_help() - return + options.parser.print_help() + return if options.output == "-": fp = sys.stdout elif options.output: @@ -58,31 +43,25 @@ def main(): fp = None if fp: print_header(fp) - if options.glob_pattern: - options.filelist = fnmatch.filter(options.filelist, - options.glob_pattern) + nerrors = 0 for i, options.filename in enumerate(options.filelist): try: if options.verbose: - print i, options.filename + print(i, options.filename) if not options.filename.endswith(".m"): - if options.verbose: - print("\tIgnored: '%s' (unexpected file type)" % - options.filename) + print("\tIgnored: '%s' (unexpected file type)" % + options.filename) continue if basename(options.filename) in options.xfiles: if options.verbose: - print "\tExcluded: '%s'" % options.filename + print("\tExcluded: '%s'" % options.filename) continue - if tar: - buf = tar.extractfile(options.filename).read() - else: - buf = open(options.filename).read() + buf = open(options.filename).read() buf = buf.replace("\r\n", "\n") - buf = buf.decode("ascii", errors="ignore") + # FIXME buf = buf.decode("ascii", errors="ignore") stmt_list = parse.parse(buf if buf[-1] == '\n' else buf + '\n') - #assert None not in stmt_list + if not stmt_list: continue if not options.no_resolve: @@ -94,16 +73,6 @@ def main(): with open(f, "w") as fp: print_header(fp) fp.write(s) - try: - py_compile.compile(f,doraise=True) - if options.execfile: - execfile(f) - except: - if options.delete_on_error: - os.unlink(f) - if options.verbose: - print "Removed",f - raise else: fp.write(s) except KeyboardInterrupt: @@ -114,7 +83,9 @@ def main(): if options.strict: break finally: - print "Errors:", nerrors + pass + if nerrors: + print("Errors:", nerrors) if __name__ == "__main__": main() diff --git a/smop/mybench.m b/smop/mybench.m deleted file mode 100644 index 581b061c..00000000 --- a/smop/mybench.m +++ /dev/null @@ -1,450 +0,0 @@ -function res = mybench(varargin) -% res = mybench('noOfRepeats', 3,... -% 'normalize',true,... -% 'onlyTests',[],... -% 'imagep',true) -% -% Benchmark script for MATLAB and Octave. -% Tested on Matlab 2009a and Octave 3.2.2 for Windows. -% -% Execution time of basic matrix manipulation function is tested along with -% integration, solving nonlinear equation, image processing functions ( if -% avaliable), saveing/loading matrices to a file system, for loop, -% binary operation,etc. In total 27 -% tests are performed (less if image processing functions not avaliable). -% -% All results are normilized against the results obtained -% using MATLAB 7.4.0.287 (R2007a) on Intel Mac OS X 10.4.11 (Intel Core Duo -% 2GHz, 2GB RAM) -% -% At the end, arithmetic and geometric means of the times obtained are -% calculated. All results obtained are stored in a txt file named -% results_.txt. -% -% INPUT -% noOfRepeats - int - number of times each test is executed (default 3) -% normalize - boolean - normalize results (default true). -% onlyTests - int vector - do only tests given (default [], i.e. do all tests) -% -% OUTPUT -% res - struct - normalized geometric mean . -% -% EXAMPLES -% res = mybench(); %perform all tests with default settings. -% res = mybench('noOfRepeats',10); %perform all tests 10 times. -% res = mybench('onlyTests',[1,5,8]); %perform only tests 1,5 and 8. -% res = mybench('noOfRepeats', 1,'normalize',false); % repeat 1 time -% %each tests and do not -% %normalize results. -% -% KNOWN_ISSUES -% Solving nonlinear equation produces info about termination - don't worry. -% -% Site: http:\\shortrecipes.blogspot.com -% Date: Nov 2009 -% - global IS_OCTAVE IS_IMAGE_PROCESSING - - %DEFAULT INPUT PARAMETERS. - conf = struct(... - 'noOfRepeats', 3,... - 'normalize',true,... - 'imagep',true,... - 'onlyTests',[]... - ); - - conf = getargs(conf, varargin); - - IS_OCTAVE = exist('OCTAVE_VERSION','builtin') > 0; - IS_IMAGE_PROCESSING = false; - NO_REPETITIONS = conf.noOfRepeats; - NORMALIZE_TIMES = conf.normalize; - - if exist('imrotate','file') > 0 && exist('imresize','file') > 0 ... - && exist('imerode','file') > 0 && conf.imagep == true - disp('Image processing toolbox found'); - IS_IMAGE_PROCESSING = true; - end - - if conf.noOfRepeats < 1 - conf.noOfRepeats = 1; - end - - clc; - - mytests=getBenchmarkTests(); - noOftests = length(mytests); - - %create output file - moVersio = strrep(version(),' ','_'); - outFileName = "foo.txt" %['results_',moVersio,'.txt']; - fid = fopen(outFileName,'w'); - - if NORMALIZE_TIMES - fprintf(fid,'%s\t%s\t%s\n',['Name_',moVersio],'Time','Norm_time'); - else - fprintf(fid,'%s\t%s\n',['Name_',moVersio],'Time_[s]'); - end - - avarage_time = 0; - - times_vector =[]; - times_vector1 =[]; % not normalized - - if isempty(conf.onlyTests) - doTheseTests = 1:noOftests; - else - doTheseTests = conf.onlyTests; - noOftests = length(conf.onlyTests); - end - - %loop over tests - for i=doTheseTests - - %display some info - fprintf(1,'Execute test %d/%d - %s\n',i,noOftests,... - mytests{i}.name); - if IS_OCTAVE, fflush(stdout); end - - try - %get input for a give test - x = mytests{i}.input(); - - %execute test and measure time - cumulative_time = 0; - cumulative_time1 = 0; - goldResult = 1; - for ii=1:NO_REPETITIONS - - fprintf(1,'%d ',ii); - if IS_OCTAVE, fflush(stdout); end - - t0=tic(); - mytests{i}.test(x); - t1=toc(t0); - - if isfield(mytests{i}, 'goldResult') && NORMALIZE_TIMES == true - goldResult = mytests{i}.goldResult; - end - - cumulative_time=cumulative_time+t1/goldResult; - cumulative_time1=cumulative_time1+t1; - - end - avarage_time = cumulative_time/NO_REPETITIONS; - avarage_time1 = cumulative_time1/NO_REPETITIONS; - times_vector(end+1) = avarage_time; - times_vector1(end+1) = avarage_time1; % not normalized - - catch - le = lasterror; - disp(le.message); - fprintf(1,'\n\n \t ... Skip to the next test ...\n\n'); - if IS_OCTAVE, fflush(stdout); end - continue - end - - - %some postprocessing if defined - if isfield(mytests{i}, 'post') - mytests{i}.post(); - end - - - %display some info - - fprintf(1,'\n\tTime %.2f [s]\n',avarage_time1); - if NORMALIZE_TIMES == true - fprintf(1,'\tNormalized time %.2f \n',avarage_time); - end - fprintf(1,'\n'); - if IS_OCTAVE, fflush(stdout); end - - if NORMALIZE_TIMES - fprintf(fid,'%s\t%f\t%f\n',mytests{i}.name,... - avarage_time1,avarage_time); - else - fprintf(fid,'%s\t%f\n',mytests{i}.name,... - avarage_time1); - end - end - - times_product = prod(times_vector); - times_mean = mean(times_vector); - times_geometric_mean = times_product^(1/length(times_vector) ); - - times_product1 = prod(times_vector1); % not normalized - times_mean1 = mean(times_vector1); - times_geometric_mean1 = times_product1^(1/length(times_vector1) ); - - res.norm_geometric_mean = times_geometric_mean; - res.norm_arithmetic_mean = times_mean; - res.norm_min = min(times_vector); - res.norm_max = max(times_vector); - - fprintf(1,'\n\t --- SUMMARY ---\n'); - - %display some info - - fprintf(1,'\n\tMean: geometric %.3f [s], arithmetic %.3f [s]',... - times_geometric_mean1,times_mean1); - fprintf(1,'\n\tMin %.3f [s], Max %.3f [s]\n\n',... - min(times_vector1),max(times_vector1)); - - - if NORMALIZE_TIMES == true - fprintf(1,'\tNormalized Mean: geometric %.3f, arithmetic %.3f',... - times_geometric_mean,times_mean); - fprintf(1,'\n\tNormalized Min %.3f [s], Max %.3f [s]\n\n',... - min(times_vector),max(times_vector)); - end - if IS_OCTAVE, fflush(stdout); end - - if NORMALIZE_TIMES - fprintf(fid,'%s\t%f\t%f\n','Geom_mean',times_geometric_mean1,... - times_geometric_mean); - else - fprintf(fid,'%s\t%f\t%f\n','Geom_mean',times_geometric_mean1); - end - - fclose(fid); - - disp(''); - disp(['End of test. File ',outFileName,' was created.']) - - %do some clean up - if exist('out_gray.png'), delete 'out_gray.png'; end - if exist('out_1.png'), delete 'out_1.png'; end - if exist('out_mtx'), delete 'out_mtx'; end - if exist('out_mtx.mat'), delete 'out_mtx.mat'; end - if exist('dlm.txt'), delete 'dlm.txt'; end - - clear IS_OCTAVE IS_IMAGE_PROCESSING; - -%%%%%%%%%%%%%%%%%%%%%%%%%% FUNCTIONS ********************** - -function s = getBenchmarkTests() -%the cell with tests name, test functions and input params. -%Each tests has the form of a structure with filelds 'name', 'test', -%'input', and optional 'post' nad 'goldResult' fields. -%'name' is a name that you want to give to your test. -%'test' is an anonymous function or a function handler. The execution time -% of this function is measured. -%'input' anonymous function that provides input data to a test. The time -% of this function is not measured. -% 'post' anonymous function that can do some postprocessing, e.g. cleanup. -% the time of 'post' funciton is not measured -% 'goldResult' is a result in seconds obtaiend on my computer. This times -% is used for the normalization of time scores. -% -% - global IS_OCTAVE IS_IMAGE_PROCESSING - - s={}; - - %s{end+1}=struct('name','interp2','test', @(x) interp2(x,2,'spline'),... - % 'input', @()rand(600),'goldResult',4.20); - - - s{end+1}=struct(... - 'name','rand',... - 'test', @(x) rand(x),... - 'input', @()4000,... - 'goldResult',1.12); - - s{end+1}=struct(... - 'name','randn',... - 'test', @(x) randn(x),... - 'input', @()4000,... - 'goldResult',0.58); - - s{end+1}=struct('name','primes','test', @(x) primes(x), 'input', @() 1e7,... - 'goldResult',0.74); - - s{end+1}=struct('name','fft2','test', @(x) fft2(x), 'input', @()rand(3000),... - 'goldResult',2.28); - - % s{end+1}=struct('name','ifft2','test', @(x) ifft2(x), 'input', @()rand(3000),... - % 'goldResult',2.979296); - - s{end+1}=struct('name','square','test', @(x) x^2, 'input', @()rand(1000),... - 'goldResult',1.35); - - s{end+1}=struct('name','inv','test', @(x) inv(x), 'input', @()rand(1000),... - 'goldResult',0.87); - - s{end+1}=struct('name','eig','test', @(x) eig(x), 'input', @()rand(1000),... - 'goldResult',9.45); - - s{end+1}=struct('name','qr','test', @(x) qr(x), 'input', @()rand(1000),... - 'goldResult',0.79); - - s{end+1}=struct('name','schur','test', @(x) schur(x), 'input', @()rand(600),... - 'goldResult',2.67); - - s{end+1}=struct('name','roots','test', @(x) roots(x), 'input', ... - @()rand(600,1),'goldResult',2.08); - - s{end+1}=struct('name','binary',... - 'test', @(x) eye(x)<1,... - 'input', @() 5000 ,... - 'goldResult',0.51); - - s{end+1}=struct('name','forLoop',... - 'test', @(x)forLoop(x),... - 'input', @() 200 ,... - 'goldResult',0.06); - - s{end+1}=struct('name','makeSparse',... - 'test', @(x) sparse(x),... - 'input', @() eye(5000) ,... - 'goldResult',0.49); - - s{end+1}=struct('name','multiplySparse',... - 'test', @(x) sparse(x)*sparse(x),... - 'input', @() eye(5000)*rand(1) ,... - 'goldResult',0.98); - - s{end+1}=struct('name','sLinearEq',... - 'test', @(x) magic(x)/rand(1,x),... - 'input', @() 2000 ,... - 'goldResult',1.94); - - % s{end+1}=struct('name','sNonLinearEq','test',... - % @(x) solveNonLinearEq(),'input', @() NaN ,'goldResult',0.07); - - s{end+1}=struct('name','saveLoadMtx','test',... - @(x) saveLoadMtx(x),'input', @() rand(1000),'goldResult',0.93); - - s{end+1}=struct('name','dlmwriteRead','test',... - @(x) dlmwriteRead(x),'input', @() rand(500),'goldResult',5.03); - - s{end+1}=struct('name','median','test', @(x) median(x(:)),... - 'input', @() rand(4000), 'goldResult',3.32); - - s{end+1}=struct('name','std','test', @(x) std(x(:)),... - 'input', @() rand(4000),'goldResult',0.84); - - % s{end+1}=struct('name','quadl','test',... - % @() quadl (@(x) x .* sin (1 ./ x) .* sqrt (abs (1 - x)), 0, 3),... - % 'input', @() NaN,'goldResult',0.038028); - - - if IS_IMAGE_PROCESSING - - s{end+1}=struct('name','doImgAndSaveAsPNG','test',... - @(x) doImgAndSaveAsPNG(x),'input',... - @() rand(1500,1500,3) ,'post', @() pause(2),... - 'goldResult',2.00 ); - - s{end+1}=struct('name','imageToGray','test', @(I) imageToGray(I),... - 'input', ... - @() imread('out_1.png'),'goldResult',0.56 ); - - s{end+1}=struct('name','imageRotate','test', @(I) imageRotate(I),... - 'input',... - @() imread('out_gray.png'), 'goldResult',2.94 ); - - s{end+1}=struct('name','imresize','test', @(I) imresize(I,1.2),... - 'input',... - @() imread('out_gray.png'), 'goldResult',1.24); - - s{end+1}=struct('name','imageFilter','test', @(I) imageFilter(I),... - 'input',... - @() imread('out_gray.png'), 'goldResult',0.20); - - s{end+1}=struct('name','imageErode','test', @(I) imageErode(I),... - 'input',... - @() imread('out_gray.png'), 'goldResult',0.46 ); - - s{end+1}=struct('name','medfilt2','test', @(I) medfilt2(I),... - 'input',... - @() magic(2000), 'goldResult',1.03 ); - - end - - % ADDITIONAL TEST FUNCTIONS -function saveLoadMtx(out_mtx) - save out_mtx; - clear out_mtx; - load out_mtx; - -function dlmwriteRead(x) - dlmwrite('dlm.txt', x); - dlmread('dlm.txt'); - - -function forLoop(x) - for i=1:x - for j=1:x - for k=1:x - i+j+k; - end - end - end - -function doImgAndSaveAsPNG(x) - %plot a surf and save it image png with 300DPI - %f=figure; - %set(f,'Visible','off'); - %surf(x); - %print(['-f',int2str(f)],'-dpng','-r200',['out_1.png']); - %close(f) - - imwrite(x,'out_1.png'); - - -function solveNonLinearEq() - [x, fval, info] = fsolve (@equationsToSolve, [1; 1;1;1]); - -function y = equationsToSolve (x) - y(1) = -2*x(1)^2 + 3*x(1)*x(2) + 4*sin(x(2)) + log(x(3)) - 6; - y(2) = 3*x(1)^2 - 2*x(2)*x(2)^2 + 3*cos(x(1)) + 4; - y(3) = 1*x(1)^2 - 2*x(1)*x(2)*x(4)^2 + 3*cos(x(1)) + 4; - y(4) = 1*x(1)^2 - 2*x(1)*x(2)*x(3)^2 + 3*cos(x(4)) + 4; - - -function imageToGray(I) - Igray = rgb2gray(I); - imwrite(Igray,'out_gray.png'); - -function imageRotate(I) - I2=imrotate(I,2); - -function imageFilter(I) - h=fspecial('sobel'); - filteredI = imfilter(I, h); - -function imageErode(I) - SE=eye(5); - erodedI = imerode(I, SE); - - - - -% Get input argumetns -function defs = getargs(defs, varglist) - l=length(varglist); - if l==0, return, end - if mod(l,2) ~=0, - disp(' !!! Odd number of parameters !!!'); - defs={}; - return - end - varnames={varglist{1:2:l}}; - varvalues={varglist{2:2:l}}; - given_vars=zeros(1,l/2); - for i=1:1:l/2 - existss=isfield(defs,varnames{i}); - given_vars(i)=existss; - end - - if min(given_vars)==0, - disp('!!! No such parameter(s):'); - disp(varnames(~given_vars)); - defs={}; - return - end - for i=1:1:l/2 - defs.(varnames{i}) = varvalues{i}; - end diff --git a/smop/node.py b/smop/node.py index 3852cd89..05998f14 100644 --- a/smop/node.py +++ b/smop/node.py @@ -1,6 +1,7 @@ # SMOP compiler -- Simple Matlab/Octave to Python compiler # Copyright 2011-2013 Victor Leikehman - + +from __future__ import print_function from collections import namedtuple from recipes import recordtype import copy,sys,inspect @@ -44,7 +45,7 @@ def wrapper(self,*args,**kwargs): try: return f(self,*args,**kwargs) except: - print "%s.%s()" % (self.__class__.__name__, f.__name__) + print("%s.%s()" % (self.__class__.__name__, f.__name__)) raise wrapper.__name__ = f.__name__ wrapper.__doc__ = f.__doc__ diff --git a/smop/options.py b/smop/options.py index b9595b7b..f3d30ef2 100644 --- a/smop/options.py +++ b/smop/options.py @@ -7,15 +7,15 @@ "smop", usage=""" - smop [options] [file.m ...file.m] + smop [OPTIONS] [FILE1.m FILE2.m ...] """, description= """ -SMOP is Small Matlab and Octave to Python compiler. - -SMOP takes MATLAB files and translates them to Python. The -name of the resulting file is derived from the name of the -source m-file unless explicitly set with -o .""", +SMOP is Small Matlab and Octave to Python +compiler, it takes MATLAB files and translates +them to Python. The names of the resulting +files are derived from the names of the source +files unless explicitly set with -o .""", epilog=""" Example: @@ -28,8 +28,6 @@ formatter_class=argparse.RawTextHelpFormatter, ) -parser.add_argument("-c", "--code") - parser.add_argument("-a", "--archive", metavar="archive.tar", help="""Read .m files from the archive. @@ -40,28 +38,26 @@ parser.add_argument("-g", "--glob-pattern", metavar="PATTERN", type=str, -help="""Apply unix glob pattern to the input -file list or to the archived files. For -example -g 'octave-4.0.2/*.m' +help="""Apply unix glob pattern to the input file +list or to the archived files. For example -g +'octave-4.0.2/*.m' Quoted from fnmatch docs: -Note that the filename separator ('/' on -Unix) is not special to this -module. [...] Similarly, filenames -starting with a period are not special -for this module, and are matched by the -* and ? patterns. """) +Note that the filename separator ('/' on Unix) +is not special to this module. [...] Similarly, +filenames starting with a period are not special +for this module, and are matched by the * and ? +patterns. """) parser.add_argument("-o", "--output", metavar="file.py", type=str, -help="""Write the results to file.py. Use --o- to send the results to the standard -output. If not specified explicitly, -output file names are derived from -input file names by replacing ".m" with -".py". For example, +help="""Write the results to file.py. Use -o- +to send the results to the standard output. +If not specified explicitly, output file names +are derived from input file names by replacing +".m" with ".py". For example, $ smop filex.m filey.m filez.m @@ -99,31 +95,24 @@ L Lex P Parse """) - + parser.add_argument("-L", "--debug-lexer", action="store_true", help="enable built-in debugging tools-") - + parser.add_argument("-P", "--debug-parser", action="store_true", help="enable built-in debugging tools") - + parser.add_argument("filelist", nargs="*", metavar="file.m", type=str) - -#parser.add_argument("--graphviz", action="store_true") - + parser.add_argument("-D","--delete-on-error", action="store_false", -help="""Borrowed from gnu make option of -the same name and functionality. After -translation to python, the resulting -py-files undergo two checks: (a) byte- -compilation using the standard py_compile -module. and (b) loading using the builtin -evalfile function. By default, -broken py-files are kept alive to allow -their examination and debugging. +help="""By default, broken py-files are +kept alive to allow their examination and +debugging. Borrowed from gnu make option of +the same name and functionality. $ smop -v --delete-on-error *.m $ rm -f libscripts.py @@ -136,51 +125,33 @@ >>> primes(9) Oops, wrong results. """) - + parser.add_argument("-H","--no-header", action="store_true", help="""use it if you plan to concatenate -generated files.""") - +the generated files.""") + parser.add_argument("-C","--no-comments", action="store_true", help="""discard multiline comments""") - + parser.add_argument("-N", "--no-numbers", action="store_true", help="""discard line-numbering information""") - + parser.add_argument("-B","--no-backend", action="store_true", help="omit code generation") - -parser.add_argument("-E","--execfile", - action="store_false", -help="""UNSAFE pass the py-file to execfile""") - + parser.add_argument("-R","--no-resolve", action="store_true", help="omit name resolution") - -#parser.add_argument("-S","--strings", default="C", -#help="""C for Octave style, F for Matlab style""") - + parser.add_argument("-T","--testing-mode", action="store_true", -help= """support special "testing" -percent-bang comments used to write -Octave test suite. When disabled, -behaves like regular comments.""") - -# parser.add_argument("-E", "--ignore-errors", -# type=int, -# metavar="N", -# dest="ignore_errors", -# action="store", -# help="""Ignore first N exceptions. -# Other useful values are -# zero -- meaning "don't ignore errors" -# minus one -- meaning "ignore all errors" """) +help= """support special "testing" percent-bang +comments used to write Octave test suite. +When disabled, behaves like regular comments.""") args = parser.parse_args(namespace=sys.modules[__name__]) diff --git a/smop/parse.py b/smop/parse.py index 0be5409a..a6fb7ecc 100644 --- a/smop/parse.py +++ b/smop/parse.py @@ -411,8 +411,12 @@ def p_expr_ident(p): p[0] = node.ident( name=p[1], lineno=p.lineno(1), + column=p.lexpos(1) - p.lexer.lexdata.rfind("\n", 0, p.lexpos(1)), lexpos=p.lexpos(1), - column=p.lexpos(1) - p.lexer.lexdata.rfind("\n", 0, p.lexpos(1))) + defs=None, + props=None, + init=None) + @exceptions @@ -847,7 +851,7 @@ def parse(buf): if "P" in options.debug: for i, pi in enumerate(p): - print i, pi.__class__.__name__, pi._backend() + print(i, pi.__class__.__name__, pi._backend()) # for i in range(len(p)): # if isinstance(p[i], node.func_stmt): diff --git a/smop/recipes.py b/smop/recipes.py index 9adf9753..a82c1f75 100644 --- a/smop/recipes.py +++ b/smop/recipes.py @@ -8,7 +8,7 @@ def recordtype(typename, field_names, verbose=False, **default_kwds): '''Returns a new class with named fields. - @keyword field_defaults: A mapping from (a subset of) field names to default + @keyword field_defaults: A mapping from (a subset of) field names to default values. @keyword default: If provided, the default value for all fields without an explicit default in `field_defaults`. @@ -35,7 +35,7 @@ def recordtype(typename, field_names, verbose=False, **default_kwds): ''' # Parse and validate the field names. Validation serves two purposes, # generating informative error messages and preventing template injection attacks. - if isinstance(field_names, basestring): + if isinstance(field_names, str): # names separated by whitespace and/or commas field_names = field_names.replace(',', ' ').split() field_names = tuple(map(str, field_names)) @@ -125,12 +125,19 @@ def __setstate__(self, state): # Execute the template string in a temporary namespace namespace = {} try: - exec template in namespace - if verbose: print template - except SyntaxError, e: + exec(template, namespace) + if verbose: print(template) + except SyntaxError as e: raise SyntaxError(e.message + ':\n' + template) cls = namespace[typename] - cls.__init__.im_func.func_defaults = init_defaults + if sys.version_info.major == 3: + cls.__init__.__defaults__ = init_defaults + elif sys.version_info.major == 2: + cls.__init__.im_func.func_defaults = init_defaults + else: + #import pdb + #pdb.set_trace() + assert 0 # For pickling to work, the __module__ variable needs to be set to the frame # where the named tuple is created. Bypass this step in enviroments where # sys._getframe is not defined (Jython for example). @@ -142,4 +149,4 @@ def __setstate__(self, state): if __name__ == '__main__': import doctest TestResults = recordtype('TestResults', 'failed, attempted') - print TestResults(*doctest.testmod()) + print(TestResults(*doctest.testmod())) diff --git a/smop/resolve.pl b/smop/resolve.pl deleted file mode 100644 index e4d8a6cc..00000000 --- a/smop/resolve.pl +++ /dev/null @@ -1,127 +0,0 @@ -:- dynamic is_definition/1. -:- dynamic is_reference/1. -:- dynamic resolve/1. -:- dynamic do_resolve/1. -:- dynamic lhs_resolve/1. - -:- op(800,xfy, (=.)). - -prog([ - solver =. matlab_function(ai,af,w), - nBlocks =. matlab_max(matlab_ravel(ai)), - [m,n] =. matlab_size(ai), - ii =. [0, 1, 0,-1], - jj =. [1, 0,-1, 0], - a =. ai, - mv =. []]). - -% b-code down stack grows right -% + + f | | -% 2 + 2 f | x | -% 3 + 2 3 f | x | y -% [] + [2,3] f | [x,y] | -% () 5 f(x,y) | | - -% ?- do_resolve(a=b). -% = -% a -% b -% [] -% () - -% () apply/2 -% [] list/_ -% =/2 -% TODO -% 0. Copy state of is_def/is_ref --> resolve statements -% 4, const rank shape -% 6. SSA -% 8. Macroexpand -% 10. parser -% 12. backend - -name_ident(Name,Ident) :- - % Given a Name, create ident(Name,Addr) - % having brand new Addr. - gensym('',Atom), - atom_number(Atom,Addr), - Ident=ident(Name,Addr), - writeln(Ident). - -cleanupall :- - retractall(is_reference(Ident)), - retractall(is_definition(Ident)). - -do_resolve(A) :- - resolve(A). - -resolve(Name) :- - atom(Name), - !, - name_ident(Name,Ident), - assertz(is_reference(Ident)). - -resolve(A) :- - number(A), - !, - writeln(A). - -resolve(A =. B) :- - !, - resolve(B), - lhs_resolve(A), - writeln(=.). - -resolve([]) :- - !, - writeln("[]"). - -resolve([A|B]) :- - !, - resolve(A), - resolve(B). - -resolve(A) :- - compound(A), - !, - compound_name_arguments(A,B,C), - resolve(B), - resolve(C), - writeln("()"). - -lhs_resolve(Name) :- % A=... - atom(Name), - !, - name_ident(Name,Ident), - assertz(is_definition(Ident)). - -%lhs_resolve(A) :- -% number(A). - -lhs_resolve(A =. B) :- % A=B... - !, - resolve(B), - lhs_resolve(A), - writeln(=.). - -lhs_resolve([]) :- - !, - writeln("[]"). - -lhs_resolve([A|B]) :- - !, - lhs_resolve(A), - lhs_resolve(B). - -lhs_resolve(A) :- % A(B)= ... - compound(A), - !, - compound_name_arguments(A,B,C), - lhs_resolve(B), - resolve(C), - writeln("()"). - -has_definitions(Name,AddrList) :- - findall(Addr, is_definition(ident(Name,Addr)), AddrList). - -% vim : syntax=prolog diff --git a/smop/resolve.py b/smop/resolve.py index fcf77c8e..a613e713 100644 --- a/smop/resolve.py +++ b/smop/resolve.py @@ -52,6 +52,7 @@ def as_networkx(t): G.add_node(uu, ident=u) if u.defs: for v in u.defs: + assert type(v) is node.ident, type(v) vv = "%s_%s_%s" % (v.name, v.lineno, v.column) G.add_node(vv, ident=v) if u.lexpos < v.lexpos: @@ -65,22 +66,22 @@ def resolve(t, symtab=None, fp=None, func_name=None): if symtab is None: symtab = {} do_resolve(t,symtab) - G = as_networkx(t) + #G = as_networkx(t) #import pdb;pdb.set_trace() - for n in G.nodes(): - u = G.node[n]["ident"] - if u.props: - pass - elif G.out_edges(n) and G.in_edges(n): - u.props = "U" # upd - #print u.name, u.lineno, u.column - elif G.in_edges(n): - u.props = "D" # def - elif G.out_edges(n): - u.props = "R" # ref - else: - u.props = "F" # ??? - G.node[n]["label"] = "%s\\n%s" % (n, u.props) +# for n in G.nodes(): +# u = G.node[n]["ident"] +# if u.props: +# pass +# elif G.out_edges(n) and G.in_edges(n): +# u.props = "U" # upd +# #print u.name, u.lineno, u.column +# elif G.in_edges(n): +# u.props = "D" # def +# elif G.out_edges(n): +# u.props = "R" # ref +# else: +# u.props = "F" # ??? +# G.node[n]["label"] = "%s\\n%s" % (n, u.props) for u in node.postorder(t): #if u.__class__ is node.func_decl: @@ -111,20 +112,20 @@ def resolve(t, symtab=None, fp=None, func_name=None): u.args = node.funcall(func_expr=node.ident("matlabarray"), args=node.expr_list([u.args])) - H = nx.connected_components(G.to_undirected()) - for i,component in enumerate(H): - for nodename in component: - if G.node[nodename]["ident"].props == "R": - has_update = 1 - break - else: - has_update = 0 - if has_update: - for nodename in component: - G.node[nodename]["ident"].props += "S" # sparse - #S = G.subgraph(nbunch) - #print S.edges() - return G +# H = nx.connected_components(G.to_undirected()) +# for i,component in enumerate(H): +# for nodename in component: +# if G.node[nodename]["ident"].props == "R": +# has_update = 1 +# break +# else: +# has_update = 0 +# if has_update: +# for nodename in component: +# G.node[nodename]["ident"].props += "S" # sparse +# #S = G.subgraph(nbunch) +# #print S.edges() +# return G def do_resolve(t,symtab): @@ -172,7 +173,7 @@ def _resolve(self,symtab): self.stmt_list._resolve(symtab) # 2nd time, intentionally # Handle the case where FOR loop is not executed for k,v in symtab_copy.items(): - symtab.setdefault(k,set()).update(v) + symtab.setdefault(k,[]).append(v) @extend(node.func_stmt) def _resolve(self,symtab): @@ -201,7 +202,7 @@ def _resolve(self,symtab): @extend(node.ident) def _lhs_resolve(self,symtab): - symtab[self.name] = set([self]) + symtab[self.name] = [self] @extend(node.if_stmt) def _resolve(self,symtab): @@ -211,7 +212,7 @@ def _resolve(self,symtab): if self.else_stmt: self.else_stmt._resolve(symtab_copy) for k,v in symtab_copy.items(): - symtab.setdefault(k,set()).update(v) + symtab.setdefault(k,[]).append(v) @extend(node.let) def _lhs_resolve(self,symtab): @@ -243,9 +244,9 @@ def _resolve(self,symtab): @extend(node.ident) def _resolve(self,symtab): if self.defs is None: - self.defs = set() + self.defs = [] try: - self.defs |= symtab[self.name] + self.defs += symtab[self.name] except KeyError: # defs == set() means name used, but not defined pass @@ -302,7 +303,7 @@ def _resolve(self,symtab): self.stmt_list._resolve(symtab) # Handle the case where WHILE loop is not executed for k,v in symtab_copy.items(): - symtab.setdefault(k,set()).update(v) + symtab.setdefault(k,[]).append(v) @extend(node.function) def _resolve(self,symtab): self.head._resolve(symtab) diff --git a/smop/rules.pl b/smop/rules.pl deleted file mode 100644 index 4970acdf..00000000 --- a/smop/rules.pl +++ /dev/null @@ -1,32 +0,0 @@ -prog([ - nBlocks = matlab_max(matlab_ravel(ai)), - [m,n] = matlab_size(ai), - ii = [0, 1, 0,-1], - jj = [1, 0,-1, 0], - a = ai, - mv = []]). - - -rewrite(matlab_max(matlab_ravel(X)), - fortran_max(X)). - -rewrite([M,N] = matlab_size(A), - [M = fortran_size(A,1), N = fortran_size(A,2)]). - -rewrite(A,A) :- atomic(A),!. - -rewrite([A|B],[C|D]) :- - !, - rewrite(A,C), - rewrite(B,D). - -rewrite(A,F) :- - compound(A), - !, - compound_name_arguments(A,B,C), - rewrite(B,D), - rewrite(C,E), - compound_name_arguments(F,D,E). - - -% vim : syntax=prolog diff --git a/smop/test_primes.py b/smop/test_primes.py index f2700d15..323091e0 100644 --- a/smop/test_primes.py +++ b/smop/test_primes.py @@ -1,3 +1,3 @@ -import octave -print octave.primes(10000) +import liboctave +print liboctave.primes(10000) From 2e0443eada87d718ddb88dcd374810b71944f2d8 Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Mon, 21 May 2018 03:24:03 +0300 Subject: [PATCH 02/10] ported to py3 --- smop/libsmop.py | 740 ++++++++++++++++++++++++++++++++++++++++++++++++ smop/version.py | 1 + 2 files changed, 741 insertions(+) create mode 100644 smop/libsmop.py create mode 100644 smop/version.py diff --git a/smop/libsmop.py b/smop/libsmop.py new file mode 100644 index 00000000..06acd2bf --- /dev/null +++ b/smop/libsmop.py @@ -0,0 +1,740 @@ +# SMOP compiler runtime support library +# Copyright 2014 Victor Leikehman + +# MIT license + +import __builtin__ + +import numpy +from numpy import sqrt,prod,exp,log,dot,multiply,inf +from numpy.fft import fft2 +from numpy.linalg import inv +from numpy.linalg import qr as _qr +try: + from scipy.linalg import schur as _schur +except ImportError: + pass +import numpy as np + +import os,sys,copy,time +from sys import stdin,stdout,stderr +try: + from scipy.io import loadmat +except: + pass +import unittest +from scipy.special import gamma +from numpy import rint as fix + +def isvector_or_scalar(a): + """ + one-dimensional arrays having shape [N], + row and column matrices having shape [1 N] and + [N 1] correspondingly, and their generalizations + having shape [1 1 ... N ... 1 1 1]. + Scalars have shape [1 1 ... 1]. + Empty arrays dont count + """ + try: + return a.size and a.ndim-a.shape.count(1) <= 1 + except: + return False +def isvector(a): + """ + one-dimensional arrays having shape [N], + row and column matrices having shape [1 N] and + [N 1] correspondingly, and their generalizations + having shape [1 1 ... N ... 1 1 1] + """ + try: + return a.ndim-a.shape.count(1) == 1 + except: + return False + +class matlabarray(np.ndarray): + """ + >>> matlabarray() + matlabarray([], shape=(0, 0), dtype=float64) + >>> matlabarray([arange(1,5), arange(1,5)]) + matlabarray([1, 2, 3, 4, 5, 1, 2, 3, 4, 5]) + >>> matlabarray(["hello","world"]) + matlabarray("helloworld") + """ + + def __new__(cls,a=[],dtype=None): + obj = np.array(a, + dtype=dtype, + copy=False, + order="F", + ndmin=2).view(cls).copy(order="F") + if obj.size == 0: + obj.shape = (0,0) + return obj + + #def __array_finalize__(self,obj): + + def __copy__(self): + return np.ndarray.copy(self,order="F") + + def __iter__(self): + """ must define iter or char won't work""" + return np.asarray(self).__iter__() + + def compute_indices(self,index): + if not isinstance(index,tuple): + index = index, + if len(index) != 1 and len(index) != self.ndim: + raise IndexError + indices = [] + for i,ix in enumerate(index): + if ix.__class__ is end: + indices.append(self.shape[i]-1+ix.n) + elif ix.__class__ is slice: + if self.size == 0 and ix.stop is None: + raise IndexError + if len(index) == 1: + n = self.size + else: + n = self.shape[i] + indices.append(np.arange((ix.start or 1)-1, + ix.stop or n, + ix.step or 1, + dtype=int)) + else: + try: + indices.append(int(ix)-1) + except: + indices.append(np.asarray(ix).astype("int32")-1) + if len(indices) == 2 and isvector(indices[0]) and isvector(indices[1]): + indices[0].shape = (-1,1) + indices[1].shape = (-1,) + return tuple(indices) + + def __getslice__(self,i,j): + if i == 0 and j == sys.maxsize: + return self.reshape(-1,1,order="F") + return self.__getitem__(slice(i,j)) + + def __getitem__(self,index): + return matlabarray(self.get(index)) + + def get(self,index): + #import pdb; pdb.set_trace() + indices = self.compute_indices(index) + if len(indices) == 1: + return np.ndarray.__getitem__(self.reshape(-1,order="F"),indices) + else: + return np.ndarray.__getitem__(self,indices) + + def __setslice__(self,i,j,value): + if i == 0 and j == sys.maxsize: + index = slice(None,None) + else: + index = slice(i,j) + self.__setitem__(index,value) + + def sizeof(self,ix): + if isinstance(ix,int): + n = ix+1 + elif isinstance(ix,slice): + n = ix.stop + elif isinstance(ix,(list,np.ndarray)): + n = max(ix)+1 + else: + assert 0,ix + if not isinstance(n,int): + raise IndexError + return n + + def __setitem__(self,index,value): + #import pdb; pdb.set_trace() + indices = self.compute_indices(index) + try: + if len(indices) == 1: + np.asarray(self).reshape(-1,order="F").__setitem__(indices,value) + else: + np.asarray(self).__setitem__(indices,value) + except (ValueError,IndexError): + #import pdb; pdb.set_trace() + if not self.size: + new_shape = [self.sizeof(s) for s in indices] + self.resize(new_shape,refcheck=0) + np.asarray(self).__setitem__(indices,value) + elif len(indices) == 1: + # One-dimensional resize is only implemented for + # two cases: + # + # a. empty matrices having shape [0 0]. These + # matries may be resized to any shape. A[B]=C + # where A=[], and B is specific -- A[1:10]=C + # rather than A[:]=C or A[1:end]=C + if self.size and not isvector_or_scalar(self): + raise IndexError("One-dimensional resize " + "works only on vectors, and " + "row and column matrices") + # One dimensional resize of scalars creates row matrices + # ai = 3 + # a(4) = 1 + # 3 0 0 1 + n = self.sizeof(indices[0]) # zero-based + if max(self.shape) == 1: + new_shape = list(self.shape) + new_shape[-1] = n + else: + new_shape = [(1 if s==1 else n) for s in self.shape] + self.resize(new_shape,refcheck=0) + np.asarray(self).reshape(-1,order="F").__setitem__(indices,value) + else: + new_shape = list(self.shape) + if self.flags["C_CONTIGUOUS"]: + new_shape[0] = self.sizeof(indices[0]) + elif self.flags["F_CONTIGUOUS"]: + new_shape[-1] = self.sizeof(indices[-1]) + self.resize(new_shape,refcheck=0) + np.asarray(self).__setitem__(indices,value) + + def __repr__(self): + return self.__class__.__name__ + repr(np.asarray(self))[5:] + + def __str__(self): + return str(np.asarray(self)) + + def __add__(self,other): + return matlabarray(np.asarray(self)+np.asarray(other)) + + def __neg__(self): + return matlabarray(np.asarray(self).__neg__()) + +class end(object): + def __add__(self,n): + self.n = n + return self + def __sub__(self,n): + self.n = -n + return self +#### +class cellarray(matlabarray): + """ + Cell array corresponds to matlab ``{}`` + + + """ + + def __new__(cls, a=[]): + """ + Create a cell array and initialize it with a. + Without arguments, create an empty cell array. + + Parameters: + a : list, ndarray, matlabarray, etc. + + >>> a=cellarray([123,"hello"]) + >>> print a.shape + (1, 2) + + >>> print a[1] + 123 + + >>> print a[2] + hello + """ + obj = np.array(a, + dtype=object, + order="F", + ndmin=2).view(cls).copy(order="F") + if obj.size == 0: + obj.shape = (0,0) + return obj + + def __getitem__(self,index): + return self.get(index) + +# def __str__(self): +# if self.ndim == 0: +# return "" +# if self.ndim == 1: +# return "".join(s for s in self) +# if self.ndim == 2: +# return "\n".join("".join(s) for s in self) +# raise NotImplementedError + + +class cellstr(matlabarray): + """ + >>> s=cellstr(char('helloworldkitty').reshape(3,5)) + >>> s + cellstr([['hello', 'world', 'kitty']], dtype=object) + >>> print s + hello + world + kitty + >>> s.shape + (1, 3) + """ + + def __new__(cls, a): + """ + Given a two-dimensional char object, + create a cell array where each cell contains + a line. + """ + obj = np.array(["".join(s) for s in a], + dtype=object, + copy=False, + order="C", + ndmin=2).view(cls).copy(order="F") + if obj.size == 0: + obj.shape = (0,0) + return obj + + def __str__(self): + return "\n".join("".join(s) for s in self.reshape(-1)) + + def __getitem__(self,index): + return self.get(index) + + +class char(matlabarray): + """ + class char is a rectangular string matrix, which + inherits from matlabarray all its features except + dtype. + + >>> s=char() + >>> s.shape + (0, 0) + + >>> s=char('helloworld') + >>> reshape(s, [2,5]) + hlool + elwrd + + >>> s=char([104, 101, 108, 108, 111, 119, 111, 114, 108, 100]) + >>> s.shape = 2,5 + >>> print s + hello + world + """ + + def __new__(cls, a=""): + if not isinstance(a,str): + a = "".join([chr(c) for c in a]) + obj = np.array(list(a), + dtype='|S1', + copy=False, + order="F", + ndmin=2).view(cls).copy(order="F") + if obj.size == 0: + obj.shape = (0,0) + return obj + + def __getitem__(self,index): + return self.get(index) + + def __str__(self): + if self.ndim == 0: + return "" + if self.ndim == 1: + return "".join(s for s in self) + if self.ndim == 2: + return "\n".join("".join(s) for s in self) + raise NotImplementedError + +class struct(object): + def __init__(self,*args): + for i in range(0,len(args),2): + setattr(self,str(args[i]),args[i+1]) + +NA = numpy.NaN + +def abs(a): + return numpy.abs(a) + +def all(a): + return numpy.all(a) + +def any(a): + return numpy.any(a) + +def arange(start,stop,step=1,**kwargs): + """ + >>> a=arange(1,10) # 1:10 + >>> size(a) + matlabarray([[ 1, 10]]) + """ + expand_value = 1 if step > 0 else -1 + return matlabarray(np.arange(start, + stop+expand_value, + step, + **kwargs).reshape(1,-1),**kwargs) +def concat(args): + """ + >>> concat([1,2,3,4,5] , [1,2,3,4,5]]) + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5] + """ + import pdb + pdb.set_trace() + t = [matlabarray(a) for a in args] + return np.concatenate(t) + +def ceil(a): + return numpy.ceil(a) + +def cell(*args): + if len(args) == 1: + args += args + return cellarray(np.zeros(args,dtype=object,order="F")) + +def clc(): + pass + +def copy(a): + return matlabarray(np.asanyarray(a).copy(order="F")) + +def deal(a,**kwargs): + #import pdb; pdb.set_trace() + return tuple([ai for ai in a.flat]) + +def disp(*args): + print (args) + +def eig(a): + u,v = np.linalg.eig(a) + return u.T + +def logical_not(a): + return numpy.logical_not(a) + +def logical_and(a,b): + return numpy.logical_and(a,b) + +def logical_or(a,b): + return numpy.logical_or(a,b) + +def exist(a,b): + if str(b) == 'builtin': + return str(a) in globals() + if str(b) == 'file': + return os.path.exists(str(a)) + raise NotImplementedError + +def false(*args): + if not args: + return False # or matlabarray(False) ??? + if len(args) == 1: + args += args + return np.zeros(args,dtype=bool,order="F") + +def find(a,n=None,d=None,nargout=1): + if d: + raise NotImplementedError + + # there is no promise that nonzero or flatnonzero + # use or will use indexing of the argument without + # converting it to array first. So we use asarray + # instead of asanyarray + if nargout == 1: + i = np.flatnonzero(np.asarray(a)).reshape(1,-1)+1 + if n is not None: + i = i.take(n) + return matlabarray(i) + if nargout == 2: + i,j = np.nonzero(np.asarray(a)) + if n is not None: + i = i.take(n) + j = j.take(n) + return (matlabarray((i+1).reshape(-1,1)), + matlabarray((j+1).reshape(-1,1))) + raise NotImplementedError + +def floor(a): + return int(numpy.floor(a)) + +def fopen(*args): + try: + fp = open(*args) + assert fp != -1 + return fp + except: + return -1 + +def fflush(fp): + fp.flush() + +def fprintf(fp,fmt,*args): + if not isinstance(fp,file): + fp = stdout + fp.write(str(fmt) % args) + +def fullfile(*args): + return os.path.join(*args) + +# implemented in "scripts/set/intersect.m" +#def intersect(a,b,nargout=1): +# if nargout == 1: +# c = sorted(set(a) & set(b)) +# if isinstance(a,str): +# return "".join(c) +# elif isinstance(a,list): +# return c +# else: +# # FIXME: the result is a column vector if +# # both args are column vectors; otherwise row vector +# return np.array(c) +# raise NotImplementedError +# +def iscellstr(a): + # TODO return isinstance(a,cellarray) and all(ischar(t) for t in a.flat) + return isinstance(a,cellarray) and all(isinstance(t,str) for t in a.flat) + +def ischar(a): + try: + return a.dtype == "|S1" + except AttributeError: + return False +# ---------------------------------------------------- +def isempty(a): + try: + return 0 in np.asarray(a).shape + except AttributeError: + return False + +def isequal(a,b): + return np.array_equal(np.asanyarray(a), + np.asanyarray(b)) + +def isfield(a,b): + return str(b) in a.__dict__.keys() + +def ismatrix(a): + return True + +def isnumeric(a): + return np.asarray(a).dtype in (int,float) + +def isscalar(a): + """np.isscalar returns True if a.__class__ is a scalar + type (i.e., int, and also immutable containers str and + tuple, but not list.) Our requirements are different""" + try: + return a.size == 1 + except AttributeError: + return np.isscalar(a) + +def length(a): + try: + return __builtin__.max(np.asarray(a).shape) + except ValueError: + return 1 + +try: + def load(a): + return loadmat(a) # FIXME +except: + pass + +def max(a, d=0, nargout=0): + if d or nargout: + raise NotImplementedError + return np.amax(a) + +def min(a, d=0, nargout=0): + if d or nargout: + raise NotImplementedError + return np.amin(a) + +def mod(a,b): + try: + return a % b + except ZeroDivisionError: + return a + +def ndims(a): + return np.asarray(a).ndim + +def numel(a): + return np.asarray(a).size + +def ones(*args,**kwargs): + if not args: + return 1 + if len(args) == 1: + args += args + return matlabarray(np.ones(args,order="F",**kwargs)) + +#def primes2(upto): +# primes=np.arange(2,upto+1) +# isprime=np.ones(upto-1,dtype=bool) +# for factor in primes[:int(math.sqrt(upto))]: +# if isprime[factor-2]: isprime[factor*2-2::factor]=0 +# return primes[isprime] +# +#def primes(*args): +# return _primes.primes(*args) + +def qr(a): + return matlabarray(_qr(np.asarray(a))) + +def rand(*args,**kwargs): + if not args: + return np.random.rand() + if len(args) == 1: + args += args + try: + return np.random.rand(np.prod(args)).reshape(args,order="F") + except: + pass + +def assert_(a,b=None,c=None): + if c: + if c >= 0: + assert (abs(a-b) < c).all() + else: + assert (abs(a-b) < abs(b*c)).all() + elif b is None: + assert a + else: + #assert isequal(a,b),(a,b) + #assert not any(a-b == 0) + assert (a==b).all() + +def shared(a): + pass + +def rand(*args,**kwargs): + """from core aka libsmop.py""" + return np.random.rand() + # if not args: + # return np.random.rand() + # if len(args) == 1: + # args += args + # try: + # return np.random.rand(np.prod(args)).reshape(args,order="F") + # except: + # pass + +def randn(*args,**kwargs): + if not args: + return np.random.randn() + if len(args) == 1: + args += args + try: + return np.random.randn(np.prod(args)).reshape(args,order="F") + except: + pass + +def ravel(a): + return np.asanyarray(a).reshape(-1,1) + +def roots(a): + + return matlabarray(np.roots(np.asarray(a).ravel())) + +def round(a): + return np.round(np.asanyarray(a)) + +def rows(a): + return np.asarray(a).shape[0] + +def schur(a): + return matlabarray(_schur(np.asarray(a))) + +def size(a, b=0, nargout=1): + """ + >>> size(zeros(3,3)) + 1 + matlabarray([[4, 4]]) + """ + s = np.asarray(a).shape + if s is (): + return 1 if b else (1,)*nargout + # a is not a scalar + try: + if b: + return s[b-1] + else: + return matlabarray(s) if nargout <= 1 else s + except IndexError: + return 1 + +def size_equal(a,b): + if a.size != b.size: + return False + for i in range(len(a.shape)): + if a.shape[i] != b.shape[i]: + return False + return True + +from numpy import sqrt +sort = __builtin__.sorted + +def strcmp(a,b): + return str(a) == str(b) + +def strread(s, format="", nargout=1): + if format == "": + a = [float(x) for x in s.split()] + return tuple(a) if nargout > 1 else np.asanyarray([a]) + raise NotImplementedError + +def strrep(a,b,c): + return str(a).replace(str(b),str(c)) + +def sum(a, dim=None): + if dim is None: + return np.asanyarray(a).sum() + else: + return np.asanyarray(a).sum(dim-1) + +def toupper(a): + return char(str(a.data).upper()) + +true = True + +def tic(): + return time.clock() + +def toc(t): + return time.clock()-t + +def true(*args): + if len(args) == 1: + args += args + return matlabarray(np.ones(args,dtype=bool,order="F")) + +def version(): + return char('0.29') + +def zeros(*args,**kwargs): + if not args: + return 0.0 + if len(args) == 1: + args += args + return matlabarray(np.zeros(args,**kwargs)) + +def isa(a,b): + return True + +def print_usage(): + raise Exception + +def function(f): + def helper(*args,**kwargs): + helper.nargin = len(args) + helper.varargin = cellarray(args) + return f(*args,**kwargs) + return helper + +def error(s): + raise s + +def isreal(a): + return True + +eps = np.finfo(float).eps +#print(np.finfo(np.float32).eps) + +if __name__ == "__main__": + import doctest + doctest.testmod() + +# vim:et:sw=4:si:tw=60 diff --git a/smop/version.py b/smop/version.py new file mode 100644 index 00000000..9e505e7c --- /dev/null +++ b/smop/version.py @@ -0,0 +1 @@ +__version__='0.38_testing' From c0d2f8af3fd14a8f856d8e74840f32aaa3afae23 Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Mon, 21 May 2018 03:52:07 +0300 Subject: [PATCH 03/10] py3 wip --- smop/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smop/version.py b/smop/version.py index 9e505e7c..2ea2309f 100644 --- a/smop/version.py +++ b/smop/version.py @@ -1 +1 @@ -__version__='0.38_testing' +__version__='0.39' From c094d632e6475b48e7d40a67688a047dac8cb799 Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Tue, 22 May 2018 06:29:59 +0300 Subject: [PATCH 04/10] I think missing version.py is fixed now --- smop/__init__.py | 6 +----- smop/main.py | 5 ++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/smop/__init__.py b/smop/__init__.py index f52c40fc..8d8b2374 100644 --- a/smop/__init__.py +++ b/smop/__init__.py @@ -1,6 +1,2 @@ # SMOP compiler -- Simple Matlab/Octave to Python compiler -# Copyright 2011-2014 Victor Leikehman - -import version -import parse,resolve,backend,main -from version import __version__ +# Copyright 2011-2018 Victor Leikehman diff --git a/smop/main.py b/smop/main.py index 899489d2..b8623203 100644 --- a/smop/main.py +++ b/smop/main.py @@ -17,14 +17,13 @@ import parse import resolve import backend -import version +from version import __version__ def print_header(fp): if options.no_header: return - print("# Autogenerated with SMOP ", - version.__version__, file=fp) + print("# Generated with SMOP ", __version__, file=fp) print("from libsmop import *", file=fp) print("#", options.filename, file=fp) From b157d024013843d36bfdece0c565d42060bcfb44 Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Tue, 22 May 2018 08:42:11 +0300 Subject: [PATCH 05/10] I think missing version.py is fixed now --- smop/main.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/smop/main.py b/smop/main.py index b8623203..298ebd3d 100644 --- a/smop/main.py +++ b/smop/main.py @@ -12,11 +12,10 @@ import traceback from os.path import basename, splitext -import lexer -import options -import parse -import resolve -import backend +from . import options +from . import parse +from . import resolve +from . import backend from version import __version__ From 12ae0ab20612ca729f602d52d36914c69d1a6832 Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Tue, 22 May 2018 17:05:22 +0300 Subject: [PATCH 06/10] INSTALL --- INSTALL | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/INSTALL b/INSTALL index 6e153a69..949e4dd6 100644 --- a/INSTALL +++ b/INSTALL @@ -1,29 +1,18 @@ smop -- matlab to python compiler -Installation with easy_install -============================== - -This is the preferred option, because easy_install takes care of -everything -- finding the dowinload site, and installing the dependences -(numpy and networkx). If you don't have easy_install, this is the -perfect time to install it. It is a part of setuptools package located -at http://pypi.python.org/pypi/setuptools. - -$ easy_install smop - -Binary Windows installer -======================== - -Download it from github then run it, but you must make sure you have -installed the dependences -- numpy and networkx. - -Installation from sources -========================= - -$ tar zxvf smop.tar.gz -$ cd smop -$ python setup.py install [--user] - -If you use the --user option, don't forget to put -your .local/bin into the search path (unix only). - +Installation from sources with pip +================================== + +This is the preferred option, because pip takes +care of everything -- finding the dowinload +site, installing the dependences (numpy and +networkx), etc. If you don't have pip, this +is the perfect time to install it. + + $ pip install smop --user + $ tar zxvf smop.tar.gz + $ cd smop + $ pip install . --user + +If you use the --user option, don't forget to +put your .local/bin into the search path (unix only). From 9a34ba8e402871ce661b7df08737d240347461d7 Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Wed, 23 May 2018 00:30:59 +0300 Subject: [PATCH 07/10] wip --- setup.py | 8 +++++++- smop/main.py | 1 + smop/version.py | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) delete mode 100644 smop/version.py diff --git a/setup.py b/setup.py index 7ce84be3..a84a844d 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,13 @@ import os from setuptools import setup -from smop.version import __version__ as __VERSION__ +try: + __VERSION__ = os.popen("git describe --tags").read().strip() +except OSError as e: + __VERSION__ = "" + +open("version.py","w").write("smop/__version__='%s'" % __VERSION__) +#from smop.version import __version__ as __VERSION__ setup( author = 'Victor Leikehman', diff --git a/smop/main.py b/smop/main.py index 298ebd3d..c0ba35f8 100644 --- a/smop/main.py +++ b/smop/main.py @@ -22,6 +22,7 @@ def print_header(fp): if options.no_header: return + print("# Running Python %s" % sys.version, file=fp) print("# Generated with SMOP ", __version__, file=fp) print("from libsmop import *", file=fp) print("#", options.filename, file=fp) diff --git a/smop/version.py b/smop/version.py deleted file mode 100644 index 2ea2309f..00000000 --- a/smop/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__='0.39' From ad875db2787cee1f8ddc80d9ca614b3cde81f9be Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Thu, 24 May 2018 07:12:06 +0300 Subject: [PATCH 08/10] wip --- smop/version.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 smop/version.py diff --git a/smop/version.py b/smop/version.py new file mode 100644 index 00000000..0f8ab1d4 --- /dev/null +++ b/smop/version.py @@ -0,0 +1 @@ +__version__='0.39.1' From aac946dc5b618d28f45ebba2b9a5e454484930ed Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Thu, 24 May 2018 16:02:49 +0300 Subject: [PATCH 09/10] wip --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a84a844d..a3c5bd26 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ except OSError as e: __VERSION__ = "" -open("version.py","w").write("smop/__version__='%s'" % __VERSION__) +open("smop/version.py","w").write("__version__='%s'" % __VERSION__) #from smop.version import __version__ as __VERSION__ setup( From 245d9c8989e304a637840d6bcbd87d8905a96101 Mon Sep 17 00:00:00 2001 From: Victor Leikehman Date: Fri, 25 May 2018 06:01:47 +0300 Subject: [PATCH 10/10] py3 --- smop/backend.py | 6 ++++-- smop/lexer.py | 2 +- smop/main.py | 5 ++--- smop/node.py | 5 +++-- smop/options.py | 4 +++- smop/parse.py | 12 ++++++------ smop/resolve.py | 5 ++--- smop/version.py | 2 +- 8 files changed, 22 insertions(+), 19 deletions(-) diff --git a/smop/backend.py b/smop/backend.py index 23005b15..e9c2aac8 100644 --- a/smop/backend.py +++ b/smop/backend.py @@ -12,8 +12,10 @@ import logging logger = logging.getLogger(__name__) -import node,options -from node import extend,exceptions + +from . import node +from . import options +from . node import extend,exceptions indent = " "*4 diff --git a/smop/lexer.py b/smop/lexer.py index 1b863d9d..527d3649 100644 --- a/smop/lexer.py +++ b/smop/lexer.py @@ -5,7 +5,7 @@ import re import ply.lex as lex from ply.lex import TOKEN -import options +from . import options tokens = [ diff --git a/smop/main.py b/smop/main.py index c0ba35f8..9caac87a 100644 --- a/smop/main.py +++ b/smop/main.py @@ -16,14 +16,13 @@ from . import parse from . import resolve from . import backend -from version import __version__ - +from . import version def print_header(fp): if options.no_header: return print("# Running Python %s" % sys.version, file=fp) - print("# Generated with SMOP ", __version__, file=fp) + print("# Generated with SMOP ", version.__version__, file=fp) print("from libsmop import *", file=fp) print("#", options.filename, file=fp) diff --git a/smop/node.py b/smop/node.py index 05998f14..9f27a283 100644 --- a/smop/node.py +++ b/smop/node.py @@ -3,9 +3,10 @@ from __future__ import print_function from collections import namedtuple -from recipes import recordtype import copy,sys,inspect -import options + +from . recipes import recordtype +from . import options # def preorder(u): # if isinstance(u,traversable): diff --git a/smop/options.py b/smop/options.py index f3d30ef2..35f083f0 100644 --- a/smop/options.py +++ b/smop/options.py @@ -1,7 +1,9 @@ import sys import argparse,textwrap from textwrap import dedent -from version import __version__ + +from . import version +from . version import __version__ parser = argparse.ArgumentParser( "smop", diff --git a/smop/parse.py b/smop/parse.py index a6fb7ecc..289a7487 100644 --- a/smop/parse.py +++ b/smop/parse.py @@ -1,12 +1,12 @@ # SMOP compiler -- Simple Matlab/Octave to Python compiler # Copyright 2011-2016 Victor Leikehman -import ply.yacc as yacc -import lexer -from lexer import tokens, raise_exception -import node -from node import exceptions -import options +from ply import yacc +from . import lexer +from . lexer import tokens, raise_exception +from . import node +from . node import exceptions +from . import options # ident properties (set in parse.py) # ---------------------------------- diff --git a/smop/resolve.py b/smop/resolve.py index a613e713..02cdd5f9 100644 --- a/smop/resolve.py +++ b/smop/resolve.py @@ -20,9 +20,8 @@ import copy -import node -from node import extend -import networkx as nx +from . import node +from . node import extend def graphviz(t, fp, func_name): diff --git a/smop/version.py b/smop/version.py index 0f8ab1d4..1c6d303a 100644 --- a/smop/version.py +++ b/smop/version.py @@ -1 +1 @@ -__version__='0.39.1' +__version__='0.39.1-3-gaac946d' \ No newline at end of file