pax_global_header00006660000000000000000000000064130324535240014513gustar00rootroot0000000000000052 comment=f765ea62064562486778f3f6d8fe94df85e6792b snuggs-1.4.1/000077500000000000000000000000001303245352400130245ustar00rootroot00000000000000snuggs-1.4.1/.travis.yml000066400000000000000000000003241303245352400151340ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" install: - "pip install pytest" - "pip install coveralls" - "pip install -e ." script: - coverage run --source=snuggs -m py.test after_success: - coveralls snuggs-1.4.1/AUTHORS.txt000066400000000000000000000002411303245352400147070ustar00rootroot00000000000000Authors ======= - Sean Gillies - Chris Holden - Bas Couwenberg - Matthew Perry snuggs-1.4.1/CHANGES.txt000066400000000000000000000012761303245352400146430ustar00rootroot00000000000000Changes ======= 1.4.1 (2017-01-02) ------------------ - Bug fix: accept OrderedDict as evaluation context to enable reliable read() indexing (#9, #10). 1.4.0 (2016-07-12) ------------------ - New feature: mathematical operators like + and * can take multiple arguments as in Hy and other Lisps (#7). 1.3.1 (2015-04-03) ------------------ - Revert to operator functions in op_map (#4). 1.3.0 (2015-03-30) ------------------ - Added nil keyword (#2). 1.2.0 (2015-03-25) ------------------ - Helpfully formatted error messages, as Python SyntaxError. 1.1.0 (2015-02-17) ------------------ - Add `map` and `partial` higher-order functions. 1.0.0 (2015-02-13) ------------------ - First release. snuggs-1.4.1/CODE_OF_CONDUCT.txt000066400000000000000000000037001303245352400160420ustar00rootroot00000000000000Contributor Code of Conduct --------------------------- As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the `Contributor Covenant`_, version 1.2.0, available at http://contributor-covenant.org/version/1/2/0/ .. _Contributor Covenant: http://contributor-covenant.org snuggs-1.4.1/LICENSE000066400000000000000000000020621303245352400140310ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Mapbox Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. snuggs-1.4.1/README.rst000066400000000000000000000056341303245352400145230ustar00rootroot00000000000000====== snuggs ====== .. image:: https://travis-ci.org/mapbox/snuggs.svg?branch=master :target: https://travis-ci.org/mapbox/snuggs .. image:: https://coveralls.io/repos/mapbox/snuggs/badge.svg :target: https://coveralls.io/r/mapbox/snuggs Snuggs are s-expressions for Numpy .. code-block:: python >>> snuggs.eval("(+ (asarray 1 1) (asarray 2 2))") array([3, 3]) Syntax ====== Snuggs wraps Numpy in expressions with the following syntax: .. code-block:: expression = "(" (operator | function) *arg ")" arg = expression | name | number | string Examples ======== Addition of two numbers ----------------------- .. code-block:: python import snuggs snuggs.eval('(+ 1 2)') # 3 Multiplication of a number and an array --------------------------------------- Arrays can be created using ``asarray``. .. code-block:: python snuggs.eval("(* 3.5 (asarray 1 1))") # array([ 3.5, 3.5]) Evaluation context ------------------ Expressions can also refer by name to arrays in a local context. .. code-block:: python snuggs.eval("(+ (asarray 1 1) b)", b=np.array([2, 2])) # array([3, 3]) This local context may be provided using keyword arguments (e.g., ``b=np.array([2, 2])``), or by passing a dictionary that stores the keys and associated array values. Passing a dictionary, specifically an ``OrderedDict``, is important when using a function or operator that references the order in which values have been provided. For example, the ``read`` function will lookup the `i-th` value passed: .. code-block:: python ctx = OrderedDict(( ('a', np.array([5, 5])), ('b', np.array([2, 2])) )) snuggs.eval("(- (read 1) (read 2))", ctx) # array([3, 3]) Functions and operators ======================= Arithmetic (``* + / -``) and logical (``< <= == != >= > & |``) operators are available. Members of the ``numpy`` module such as ``asarray()``, ``mean()``, and ``where()`` are also available. .. code-block:: python snuggs.eval("(mean (asarray 1 2 4))") # 2.3333333333333335 .. code-block:: python snuggs.eval("(where (& tt tf) 1 0)", tt=numpy.array([True, True]), tf=numpy.array([True, False])) # array([1, 0]) Higher-order functions ====================== New in snuggs 1.1 are higher-order functions ``map`` and ``partial``. .. code-block:: python snuggs.eval("((partial * 2) 2)") # 4 snuggs.eval('(asarray (map (partial * 2) (asarray 1 2 3)))') # array([2, 4, 6]) Performance notes ================= Snuggs makes simple calculator programs possible. None of the optimizations of, e.g., `numexpr `__ (multithreading, elimination of temporary data, etc) are currently available. If you're looking to combine Numpy with a more complete Lisp, see `Hy `__: .. code-block:: clojure => (import numpy) => (* 2 (.asarray numpy [1 2 3])) array([2, 4, 6]) snuggs-1.4.1/setup.py000066400000000000000000000017211303245352400145370ustar00rootroot00000000000000from codecs import open as codecs_open from setuptools import setup, find_packages # Get the long description from the relevant file with codecs_open('README.rst', encoding='utf-8') as f: long_description = f.read() with open('snuggs/__init__.py') as f: for line in f: if line.startswith('__version__'): version = line.split('=')[1] version = version.strip().strip('"') break setup(name='snuggs', version=version, description=u"Snuggs are s-expressions for Numpy", long_description=long_description, classifiers=[], keywords='', author=u"Sean Gillies", author_email='sean@mapbox.com', url='https://github.com/mapbox/snuggs', license='MIT', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=False, install_requires=['click', 'numpy', 'pyparsing'], extras_require={'test': ['pytest']}) snuggs-1.4.1/snuggs/000077500000000000000000000000001303245352400143325ustar00rootroot00000000000000snuggs-1.4.1/snuggs/__init__.py000066400000000000000000000120431303245352400164430ustar00rootroot00000000000000""" Snuggs are s-expressions for Numpy. """ from collections import OrderedDict import functools import itertools import operator import re import sys from pyparsing import ( alphanums, ZeroOrMore, nums, oneOf, Word, Literal, Combine, QuotedString, ParseException, Forward, Group, CaselessLiteral, Optional, alphas, OneOrMore, ParseResults) import numpy __all__ = ['eval'] __version__ = "1.4.1" # Python 2-3 compatibility string_types = (str,) if sys.version_info[0] >= 3 else (basestring,) class Context(object): def __init__(self): self._data = OrderedDict() def add(self, name, val): self._data[name] = val def get(self, name): return self._data[name] def lookup(self, index, subindex=None): s = list(self._data.values())[int(index) - 1] if subindex: return s[int(subindex) - 1] else: return s def clear(self): self._data = OrderedDict() _ctx = Context() class ctx(object): def __init__(self, kwd_dict=None, **kwds): self.kwds = kwd_dict or kwds def __enter__(self): _ctx.clear() for k, v in self.kwds.items(): _ctx.add(k, v) return self def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): self.kwds = None _ctx.clear() class ExpressionError(SyntaxError): """Snuggs specific syntax errors""" filename = "" lineno = 1 op_map = { '*': lambda *args: functools.reduce(lambda x, y: operator.mul(x, y), args), '+': lambda *args: functools.reduce(lambda x, y: operator.add(x, y), args), '/': lambda *args: functools.reduce(lambda x, y: operator.truediv(x, y), args), '-': lambda *args: functools.reduce(lambda x, y: operator.sub(x, y), args), '&': lambda *args: functools.reduce(lambda x, y: operator.and_(x, y), args), '|': lambda *args: functools.reduce(lambda x, y: operator.or_(x, y), args), '<': operator.lt, '<=': operator.le, '==': operator.eq, '!=': operator.ne, '>=': operator.ge, '>': operator.gt} def asarray(*args): if len(args) == 1 and hasattr(args[0], '__iter__'): return numpy.asanyarray(list(args[0])) else: return numpy.asanyarray(list(args)) func_map = { 'asarray': asarray, 'read': _ctx.lookup, 'take': lambda a, idx: numpy.take(a, idx - 1, axis=0)} higher_func_map = { 'map': map if sys.version_info[0] >= 3 else itertools.imap, 'partial': functools.partial} # Definition of the grammar. decimal = Literal('.') e = CaselessLiteral('E') sign = Literal('+') | Literal('-') number = Word(nums) name = Word(alphas) nil = Literal('nil').setParseAction(lambda s, l, t: [None]) def resolve_var(s, l, t): try: return _ctx.get(t[0]) except KeyError: err = ExpressionError( "name '%s' is not defined" % t[0]) err.text = s err.offset = l + 1 raise err var = name.setParseAction(resolve_var) integer = Combine( Optional(sign) + number).setParseAction(lambda s, l, t: int(t[0])) real = Combine( integer + decimal + Optional(number) + Optional(e + integer)).setParseAction(lambda s, l, t: float(t[0])) string = QuotedString("'") | QuotedString('"') lparen = Literal('(').suppress() rparen = Literal(')').suppress() op = oneOf(' '.join(op_map.keys())).setParseAction( lambda s, l, t: op_map[t[0]]) def resolve_func(s, l, t): try: return func_map[t[0]] if t[0] in func_map else getattr(numpy, t[0]) except AttributeError: err = ExpressionError( "'%s' is not a function or operator" % t[0]) err.text = s err.offset = l + 1 raise err func = Word(alphanums + '_').setParseAction(resolve_func) higher_func = oneOf('map partial').setParseAction( lambda s, l, t: higher_func_map[t[0]]) func_expr = Forward() higher_func_expr = Forward() expr = higher_func_expr | func_expr operand = higher_func_expr | func_expr | nil | var | real | integer | string func_expr << Group( lparen + (higher_func_expr | op | func) + operand + ZeroOrMore(operand) + rparen) higher_func_expr << Group( lparen + higher_func + (nil | higher_func_expr | op | func) + ZeroOrMore(operand) + rparen) def processArg(arg): if not isinstance(arg, ParseResults): return arg else: return processList(arg) def processList(lst): args = [processArg(x) for x in lst[1:]] func = processArg(lst[0]) return func(*args) def handleLine(line): try: result = expr.parseString(line) return processList(result[0]) except ParseException as exc: text = str(exc) m = re.search(r'(Expected .+) \(at char (\d+)\), \(line:(\d+)', text) msg = m.group(1) if 'map|partial' in msg: msg = "expected a function or operator" err = ExpressionError(msg) err.text = line err.offset = int(m.group(2)) + 1 raise err def eval(source, kwd_dict=None, **kwds): kwd_dict = kwd_dict or kwds with ctx(kwd_dict): return handleLine(source) snuggs-1.4.1/test_snuggs.py000066400000000000000000000144751303245352400157560ustar00rootroot00000000000000from collections import OrderedDict import numpy import pytest import snuggs # Fixtures follow the tests. See the end of the file. def test_integer(): assert list(snuggs.integer.parseString('1')) == [1] def test_real(): assert list(snuggs.real.parseString('1.1')) == [1.1] def test_int_expr(): assert snuggs.eval('(+ 1 2)') == 3 def test_int_mult_expr(): assert snuggs.eval('(+ 1 2 3)') == 6 def test_real_expr(): assert round(snuggs.eval('(* 0.1 0.2)'), 3) == 0.02 def test_int_real_expr(): assert snuggs.eval('(+ 2 1.1)') == 3.1 def test_real_int_expr(): assert snuggs.eval('(+ 1.1 2)') == 3.1 def test_arr_var(ones): r = snuggs.eval('(+ foo 0)', foo=ones) assert list(r.flatten()) == [1, 1, 1, 1] def test_arr_lookup(ones): kwargs = OrderedDict((('foo', ones), ('bar', 2.0 * ones), ('a', 3.0 * ones))) r = snuggs.eval('(read 1)', kwargs) assert list(r.flatten()) == [1, 1, 1, 1] @pytest.mark.xfail(reason="Keyword argument order can't be relied on") def test_arr_lookup_kwarg_order(ones): kwargs = OrderedDict((('foo', ones), ('bar', 2.0 * ones), ('a', 3.0 * ones))) r = snuggs.eval('(read 1)', **kwargs) assert list(r.flatten()) == [1, 1, 1, 1] def test_arr_lookup_2(ones): r = snuggs.eval('(read 1 1)', foo=ones) assert list(r.flatten()) == [1, 1] def test_arr_take(ones): r = snuggs.eval('(take foo 1)', foo=ones) assert list(r.flatten()) == [1, 1] r = snuggs.eval('(take foo 2)', foo=ones) assert list(r.flatten()) == [1, 1] def test_int_arr_expr(ones): result = snuggs.eval('(+ foo 1)', foo=ones) assert list(result.flatten()) == [2, 2, 2, 2] def test_int_arr_expr_by_name(ones): result = snuggs.eval('(+ (read 1) 1.5)', foo=ones) assert list(result.flatten()) == [2.5, 2.5, 2.5, 2.5] def test_int_arr_read(ones): result = snuggs.eval('(+ (read 1 1) 1.5)', foo=ones) assert list(result.flatten()) == [2.5, 2.5] def test_list(ones): result = snuggs.eval( '(asarray (take foo 1) (take foo 1) (take bar 1) (take bar 1))', foo=ones, bar=ones) assert list(result.flatten()) == [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] def test_eq(ones): ones[0][0] = 2 result = snuggs.eval('(== foo 1)', foo=ones) assert list(result.flatten()) == [False, True, True, True] def test_or(truetrue, truefalse): result = snuggs.eval( '(| foo bar)', foo=truetrue, bar=truefalse) assert list(result.flatten()) == [True, True] def test_and(truetrue, truefalse): result = snuggs.eval( '(& foo bar)', foo=truetrue, bar=truefalse) assert list(result.flatten()) == [True, False] def test_ones_like(truefalse): result = snuggs.eval("(ones_like foo 'uint8')", foo=truefalse) assert list(result.flatten()) == [1.0, 1.0] def test_full_like(truefalse): result = snuggs.eval("(full_like foo 3.14 'float64')", foo=truefalse) assert list(result.flatten()) == [3.14, 3.14] result = snuggs.eval('(full_like foo 3.14 "float64")', foo=truefalse) assert list(result.flatten()) == [3.14, 3.14] def test_ufunc(truetrue, truefalse): result = snuggs.eval( '(where (& foo bar) 1 0)', foo=truetrue, bar=truefalse) assert list(result.flatten()) == [1.0, 0.0] def test_partial(): result = snuggs.eval('((partial * 2) 2)') assert result == 4 def test_map_func(): result = snuggs.eval('(map sqrt (asarray 1 4 9))') assert list(result) == [1, 2, 3] def test_map_partial(): result = snuggs.eval('(map (partial * 2) (asarray 1 2 3))') assert list(result) == [2, 4, 6] def test_map_asarray(): result = snuggs.eval('(asarray (map (partial * 2) (asarray 1 2 3)))') assert list(result) == [2, 4, 6] def test_multi_operator_array(ones): result = snuggs.eval( '(+ ones (/ ones 1 0.5) (* ones 1 3))', ones=ones) assert list(result.flatten()) == [6.0] * 4 def test_nil(): assert snuggs.eval('(== nil nil)') assert not snuggs.eval('(== 1 nil)') assert not snuggs.eval('(== nil 1)') assert snuggs.eval('(!= 1 nil)') assert snuggs.eval('(!= nil 1)') def test_masked_arr(): foo = numpy.ma.masked_equal(numpy.array([0, 0, 0, 1], dtype='uint8'), 0) r = snuggs.eval('(+ foo 1)', foo=foo) assert list(r.data.flatten()) == [0, 0, 0, 2] assert list(r.flatten()) == [numpy.ma.masked, numpy.ma.masked, numpy.ma.masked, 2] # Parse and syntax error testing. def test_missing_closing_paren(): with pytest.raises(SyntaxError) as excinfo: snuggs.eval("(+ 1 2") assert excinfo.value.lineno == 1 assert excinfo.value.offset == 7 exception_options = ['expected a function or operator', 'Expected {Forward: ... | Forward: ...}'] assert str(excinfo.value) in exception_options def test_missing_func(): with pytest.raises(SyntaxError) as excinfo: snuggs.eval("(0 1 2)") assert excinfo.value.lineno == 1 assert excinfo.value.offset == 2 assert str(excinfo.value) == "'0' is not a function or operator" def test_missing_func2(): with pytest.raises(SyntaxError) as excinfo: snuggs.eval("(# 1 2)") assert excinfo.value.lineno == 1 assert excinfo.value.offset == 2 exception_options = ['expected a function or operator', 'Expected {Forward: ... | Forward: ...}'] assert str(excinfo.value) in exception_options def test_undefined_var(): with pytest.raises(SyntaxError) as excinfo: snuggs.eval("(+ 1 bogus)") assert excinfo.value.lineno == 1 assert excinfo.value.offset == 6 assert str(excinfo.value) == "name 'bogus' is not defined" def test_bogus_higher_order_func(): with pytest.raises(SyntaxError) as excinfo: snuggs.eval("((bogus * 2) 2)") assert excinfo.value.lineno == 1 assert excinfo.value.offset == 3 exception_options = ['expected a function or operator', 'Expected {Forward: ... | Forward: ...}'] assert str(excinfo.value) in exception_options def test_type_error(): with pytest.raises(TypeError): snuggs.eval("(+ 1 'bogus')") # Fixtures. @pytest.fixture def ones(): return numpy.ones((2, 2)) @pytest.fixture def truetrue(): return numpy.array([True, True]) @pytest.fixture def truefalse(): return numpy.array([True, False])