diff options
author | Paul McGuire <ptmcg@austin.rr.com> | 2019-06-29 02:14:57 -0500 |
---|---|---|
committer | Paul McGuire <ptmcg@austin.rr.com> | 2019-06-29 02:14:57 -0500 |
commit | f5de46966a55b8c651f7ff92440665af02567df4 (patch) | |
tree | 5967a3d9fc6caa04307c60ab51c3fd7bca632a9d | |
parent | 3f01989e4ee04bcb2d71008efa989f48bd7a5123 (diff) | |
download | pyparsing-git-f5de46966a55b8c651f7ff92440665af02567df4.tar.gz |
Fix up changes to parse reals without leading digits before the decimal, and add unit tests
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | pyparsing.py | 6 | ||||
-rw-r--r-- | unitTests.py | 87 |
3 files changed, 95 insertions, 3 deletions
@@ -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 |