pax_global_header00006660000000000000000000000064135404761750014526gustar00rootroot0000000000000052 comment=84898c684f9abcbf4d88efe64b0de1a06ae4db56 snuggs-1.4.7/000077500000000000000000000000001354047617500130455ustar00rootroot00000000000000snuggs-1.4.7/.gitignore000066400000000000000000000000561354047617500150360ustar00rootroot00000000000000.cache .coverage __pycache__ *.pyc *.egg-info snuggs-1.4.7/.travis.yml000066400000000000000000000006251354047617500151610ustar00rootroot00000000000000dist: xenial language: python python: - "2.7" - "3.6" - "3.7" install: - "pip install pytest-cov coveralls pyparsing~=2.0" - "pip install -e .[test]" script: - python -m pytest --cov snuggs --cov-report term-missing after_success: - coveralls deploy: on: tags: true condition: "$TRAVIS_PYTHON_VERSION = 3.6" provider: pypi user: __token__ distributions: "sdist bdist_wheel" snuggs-1.4.7/AUTHORS.txt000066400000000000000000000002411354047617500147300ustar00rootroot00000000000000Authors ======= - Sean Gillies - Chris Holden - Bas Couwenberg - Matthew Perry snuggs-1.4.7/CHANGES.txt000066400000000000000000000033471354047617500146650ustar00rootroot00000000000000Changes ======= 1.4.7 (2019-09-18) ------------------ - The snuggs tests of syntax errors no longer assert a specific pyparsing exception message as the format of these messages is not stable (#15). Previous versions of snuggs had no other issues with pyparsing 2.3 or 2.4. 1.4.6 (2019-05-15) ------------------ - Tests were failing on Python 2.7 (#20, #21) due to loss of precision in str(num). This has been fixed by using repr(num). 1.4.5 (2019-05-14) ------------------ - Replace custom integer and real parsers with pyparsing_common's number (#19). 1.4.4 (2019-05-14) ------------------ - Fix for a reported bug in parsing negative real numbers (#16) and for an unreported bug in parsing numbers in exponential notation (thank you, hypothesis!). - Add a "test" dependency on hypothesis. 1.4.3 (2019-02-25) ------------------ - Add LICENSE to distributions (#11). - Remove click from requirements (#12). - Allow a wider range of valid variable and parameter names (#13). 1.4.2 (2018-06-07) ------------------ - Add missing docstrings and improve existing ones. 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.7/CODE_OF_CONDUCT.txt000066400000000000000000000037001354047617500160630ustar00rootroot00000000000000Contributor 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.7/LICENSE000066400000000000000000000020621354047617500140520ustar00rootroot00000000000000The 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.7/MANIFEST.in000066400000000000000000000001771354047617500146100ustar00rootroot00000000000000include CHANGES.txt AUTHORS.txt LICENSE VERSION.txt README.rst setup.py CODE_OF_CONDUCT.txt test_snuggs.py exclude MANIFEST.in snuggs-1.4.7/README.rst000066400000000000000000000056341354047617500145440ustar00rootroot00000000000000====== 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.7/setup.py000066400000000000000000000017511354047617500145630ustar00rootroot00000000000000"""Build the snuggs package.""" from 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=["numpy", "pyparsing>=2.1.6"], extras_require={"test": ["pytest", "hypothesis"]}, ) snuggs-1.4.7/snuggs/000077500000000000000000000000001354047617500143535ustar00rootroot00000000000000snuggs-1.4.7/snuggs/__init__.py000066400000000000000000000122761354047617500164740ustar00rootroot00000000000000"""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, pyparsing_common) import numpy __all__ = ['eval'] __version__ = "1.4.7" # Python 2-3 compatibility string_types = (str,) if sys.version_info[0] >= 3 else (basestring,) # flake8: noqa 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): """A Snuggs-specific syntax error.""" 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 = pyparsing_common.identifier 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) 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 | pyparsing_common.number | 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): """Evaluate a snuggs expression. Parameters ---------- source : str Expression source. kwd_dict : dict A dict of items that form the evaluation context. Deprecated. kwds : dict A dict of items that form the valuation context. Returns ------- object """ kwd_dict = kwd_dict or kwds with ctx(kwd_dict): return handleLine(source) snuggs-1.4.7/test_snuggs.py000066400000000000000000000142261354047617500157710ustar00rootroot00000000000000from collections import OrderedDict from hypothesis import given from hypothesis.strategies import floats, integers import numpy import pytest import snuggs @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]) @given(integers()) def test_integer_operand(num): assert list(snuggs.operand.parseString(repr(num))) == [num] @given(floats(allow_infinity=False, allow_nan=False)) def test_real_operand(num): assert list(snuggs.operand.parseString(repr(num))) == [num] 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] def test_arr_var_long(ones): r = snuggs.eval('(+ FOO_BAR_42 0)', FOO_BAR_42=ones) 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 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 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 def test_type_error(): with pytest.raises(TypeError): snuggs.eval("(+ 1 'bogus')") def test_negative_decimal(): """Negative decimals parse correctly""" assert snuggs.eval("(< -0.9 0)")