summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul McGuire <ptmcg@austin.rr.com>2019-06-29 02:14:57 -0500
committerPaul McGuire <ptmcg@austin.rr.com>2019-06-29 02:14:57 -0500
commitf5de46966a55b8c651f7ff92440665af02567df4 (patch)
tree5967a3d9fc6caa04307c60ab51c3fd7bca632a9d
parent3f01989e4ee04bcb2d71008efa989f48bd7a5123 (diff)
downloadpyparsing-git-f5de46966a55b8c651f7ff92440665af02567df4.tar.gz
Fix up changes to parse reals without leading digits before the decimal, and add unit tests
-rw-r--r--CHANGES5
-rw-r--r--pyparsing.py6
-rw-r--r--unitTests.py87
3 files changed, 95 insertions, 3 deletions
diff --git a/CHANGES b/CHANGES
index c731b33..037411a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -13,6 +13,11 @@ Version 2.4.1 -
indicating that all previously-defined parse actions for the
expression should be cleared.
+- Modified pyparsing_common.real and sci_real to parse reals
+ without leading integer digits before the decimal point,
+ consistent with Python real number formats. Original PR #98
+ submitted by ansobolev.
+
- Modified runTests to call postParse function before dumping out
the parsed results - allows for postParse to add further results,
such as indications of additional validation success/failure.
diff --git a/pyparsing.py b/pyparsing.py
index 3688181..fe9b8cb 100644
--- a/pyparsing.py
+++ b/pyparsing.py
@@ -96,7 +96,7 @@ classes inherit from. Use the docstrings for examples of how to:
"""
__version__ = "2.4.1"
-__versionTime__ = "29 Jun 2019 06:17 UTC"
+__versionTime__ = "29 Jun 2019 06:56 UTC"
__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>"
import string
@@ -6166,10 +6166,10 @@ class pyparsing_common:
"""mixed integer of the form 'integer - fraction', with optional leading integer, returns float"""
mixed_integer.addParseAction(sum)
- real = Regex(r'[+-]?\d*\.\d*').setName("real number").setParseAction(convertToFloat)
+ real = Regex(r'[+-]?(:?\d+\.\d*|\.\d+)').setName("real number").setParseAction(convertToFloat)
"""expression that parses a floating point number and returns a float"""
- sci_real = Regex(r'[+-]?\d*([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat)
+ sci_real = Regex(r'[+-]?(:?\d+(:?[eE][+-]?\d+)|(:?\d+\.\d*|\.\d+)(:?[eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat)
"""expression that parses a floating point number with optional
scientific notation and returns a float"""
diff --git a/unitTests.py b/unitTests.py
index 4c6c9ab..f3be763 100644
--- a/unitTests.py
+++ b/unitTests.py
@@ -3324,6 +3324,93 @@ class CommonExpressionsTest(ParseTestCase):
self.assertEqual(type(result[0]), type(expected), "numeric parse failed (wrong type) (%s should be %s)" % (type(result[0]), type(expected)))
+class NumericExpressionsTest(ParseTestCase):
+ def runTest(self):
+ import pyparsing as pp
+ ppc = pp.pyparsing_common
+
+ real = ppc.real().setParseAction(None)
+ sci_real = ppc.sci_real().setParseAction(None)
+ signed_integer = ppc.signed_integer().setParseAction(None)
+
+ from itertools import product
+
+ def make_tests():
+ leading_sign = ['+', '-', '']
+ leading_digit = ['0', '']
+ dot = ['.', '']
+ decimal_digit = ['1', '']
+ e = ['e', 'E', '']
+ e_sign = ['+', '-', '']
+ e_int = ['22', '']
+ stray = ['9', '.', '']
+
+ seen = set()
+ for parts in product(leading_sign, stray, leading_digit, dot, decimal_digit, stray, e, e_sign, e_int,
+ stray):
+ parts_str = ''.join(parts)
+ if parts_str in seen:
+ continue
+ seen.add(parts_str)
+ yield parts_str
+
+ print_(len(seen), "tests produced")
+
+ # collect tests into valid/invalid sets, depending on whether they evaluate to valid Python floats
+ valid_ints = set()
+ valid_reals = set()
+ valid_sci_reals = set()
+ invalid_ints = set()
+ invalid_reals = set()
+ invalid_sci_reals = set()
+
+ for test_str in make_tests():
+ if '.' in test_str or 'e' in test_str.lower():
+ try:
+ float(test_str)
+ except ValueError:
+ invalid_sci_reals.add(test_str)
+ if 'e' not in test_str.lower():
+ invalid_reals.add(test_str)
+ else:
+ valid_sci_reals.add(test_str)
+ if 'e' not in test_str.lower():
+ valid_reals.add(test_str)
+
+ try:
+ int(test_str)
+ except ValueError:
+ invalid_ints.add(test_str)
+ else:
+ valid_ints.add(test_str)
+
+ suppress_results = {'printResults': False}
+ success, test_results = real.runTests(sorted(valid_reals, key=len), **suppress_results)
+ # if not success:
+ # for test_string, result in test_results:
+ # if isinstance(result, Exception):
+ # print("{!r}: {}".format(test_string, result))
+
+ print_("real", ('FAIL', 'PASS')[success], "valid tests ({})".format(len(valid_reals)))
+ self.assertTrue(success, "failed real valid tests")
+ success, _ = sci_real.runTests(sorted(valid_sci_reals, key=len), **suppress_results)
+ print_("sci_real", ('FAIL', 'PASS')[success], "valid tests ({})".format(len(valid_sci_reals)))
+ self.assertTrue(success, "failed sci_real valid tests")
+ success, _ = signed_integer.runTests(sorted(valid_ints, key=len), **suppress_results)
+ print_("signed_integer", ('FAIL', 'PASS')[success], "valid tests ({})".format(len(valid_ints)))
+ self.assertTrue(success, "failed signed_integer valid tests")
+
+ success, _ = real.runTests(sorted(invalid_reals, key=len), failureTests=True, **suppress_results)
+ print_("real", ('FAIL', 'PASS')[success], "invalid tests ({})".format(len(invalid_reals)))
+ self.assertTrue(success, "failed real invalid tests")
+ success, _ = sci_real.runTests(sorted(invalid_sci_reals, key=len), failureTests=True, **suppress_results)
+ print_("sci_real", ('FAIL', 'PASS')[success], "invalid tests ({})".format(len(invalid_sci_reals)))
+ self.assertTrue(success, "failed sci_real invalid tests")
+ success, _ = signed_integer.runTests(sorted(invalid_ints, key=len), failureTests=True, **suppress_results)
+ print_("signed_integer", ('FAIL', 'PASS')[success], "invalid tests ({})".format(len(invalid_ints)))
+ self.assertTrue(success, "failed signed_integer invalid tests")
+
+
class TokenMapTest(ParseTestCase):
def runTest(self):
from pyparsing import tokenMap, Word, hexnums, OneOrMore