Repository: mapbox/snuggs (language: python)

Issue:
Negative decimal numbers not parsed correctly
Came here via rasterio calc. Apparently snuggs does not parse negative decimal numbers correctly. 

Below statement returns False:
```
snuggs.eval("(< -0.9 0)")
```
Comparing with a whole negative number returns True:
```
snuggs.eval("(< -1 0)")
```
Apparently the parser misses the sign when parsing a decimal number above -1. Compare:
```
snuggs.eval("(+ -0.9 0)")
snuggs.eval("(+ -1.7 0)")
```



Discussion / hints:

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index e82f43c..46624b4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,11 @@
+dist: xenial
 language: python
 python:
-  - "2.7"
   - "3.6"
+  - "3.7"
 install:
   - "pip install pytest-cov coveralls pyparsing==2.3.1"
-  - "pip install -e ."
+  - "pip install -e .[test]"
 script: 
   - python -m pytest --cov snuggs --cov-report term-missing
 after_success:
diff --git a/CHANGES.txt b/CHANGES.txt
index 77c5657..8a21018 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,13 @@
 Changes
 =======
 
+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).
diff --git a/setup.py b/setup.py
index ba19506..ca58cda 100644
--- a/setup.py
+++ b/setup.py
@@ -30,5 +30,5 @@
     include_package_data=True,
     zip_safe=False,
     install_requires=["numpy", "pyparsing"],
-    extras_require={"test": ["pytest"]},
+    extras_require={"test": ["pytest", "hypothesis"]},
 )
diff --git a/snuggs/__init__.py b/snuggs/__init__.py
index 3512666..85e5ca1 100644
--- a/snuggs/__init__.py
+++ b/snuggs/__init__.py
@@ -16,7 +16,7 @@
 
 
 __all__ = ['eval']
-__version__ = "1.4.3"
+__version__ = "1.4.4"
 
 # Python 2-3 compatibility
 string_types = (str,) if sys.version_info[0] >= 3 else (basestring,)  # flake8: noqa
@@ -126,8 +126,9 @@ def resolve_var(s, l, t):
     number).setParseAction(lambda s, l, t: int(t[0]))
 
 real = Combine(
+    Optional(sign) +
     integer +
-    decimal + Optional(number) +
+    Optional(decimal) + Optional(number) +
     Optional(e + integer)).setParseAction(lambda s, l, t: float(t[0]))
 
 string = QuotedString("'") | QuotedString('"')


Test patch:
diff --git a/test_snuggs.py b/test_snuggs.py
index f6bc983..0d1ad97 100644
--- a/test_snuggs.py
+++ b/test_snuggs.py
@@ -1,19 +1,36 @@
 from collections import OrderedDict
 
+from hypothesis import given
+from hypothesis.strategies import floats, integers
 import numpy
 import pytest
 
 import snuggs
 
-# Fixtures follow the tests. See the end of the file.
+
+@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])
 
 
-def test_integer():
-    assert list(snuggs.integer.parseString('1')) == [1]
+@given(integers())
+def test_integer(num):
+    assert list(snuggs.integer.parseString(str(num))) == [num]
 
 
-def test_real():
-    assert list(snuggs.real.parseString('1.1')) == [1.1]
+@given(floats(allow_infinity=False, allow_nan=False))
+def test_real(num):
+    assert list(snuggs.real.parseString(str(num))) == [num]
 
 
 def test_int_expr():
@@ -225,17 +242,6 @@ def test_type_error():
         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])
+def test_negative_decimal():
+    """Negative decimals parse correctly"""
+    assert snuggs.eval("(< -0.9 0)")