pyparsing / pyparsing

Python library for creating PEG parsers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

"" as add_parse_action's result removes result name from ParseResults

niccokunzmann opened this issue · comments

It might be a use-case to remove the result name from the ParseResults in a parse action. However, it looks a bit like an inconsistency to me: "" removes the named value but a filled string does not.

Tests:

import pyparsing as pp
import pytest


def assert_X_is_present(grammar, X):
    """Check that X is present and has a certain value."""
    parse_results = grammar.parse_string("a")
    print(parse_results, list(parse_results.items()))
    assert "X" in parse_results, f"value = {repr(X)}"
    assert parse_results["X"] == X

@pytest.mark.parametrize("value", 
    [
        "x",
        "",  # removes named value
        True,
        False,
        1,
        0,
        None, # consistent with test_no_parse_action 
        b"",
        b"a",
    ]
)
def test_with_parse_action(value):
    """Check which values are in the named result."""
    print("value =", repr(value))
    grammar = (pp.Suppress("a") + pp.ZeroOrMore("x")).add_parse_action(lambda p: value).set_results_name("X")
    assert_X_is_present(grammar, value)


def test_no_parse_action():
    """Do not add a parse result."""
    grammar = (pp.Suppress("a") + pp.ZeroOrMore("x")).set_results_name("X")
    assert_X_is_present(grammar, "")

These are the tests running:

py39 installed: attrs==22.2.0,black==23.1.0,cffi==1.15.0,click==8.1.3,coverage==6.5.0,cssselect==1.2.0,distlib==0.3.6,exceptiongroup==1.1.0,filelock==3.9.0,greenlet==0.4.13,hpy==0.0.3,iniconfig==2.0.0,inkex @ git+https://gitlab.com/inkscape/extensions@b47d2bc6b11595cd6ecbf4d16aa59d9f5e597141,lxml==4.9.2,mypy-extensions==1.0.0,numpy==1.24.2,packaging==23.0,pathspec==0.11.0,Pillow==9.4.0,platformdirs==3.0.0,pluggy==1.0.0,py==1.11.0,pycairo==1.23.0,pycparser==2.21,PyGObject==3.42.2,pyparsing==3.0.9,pypdf==3.5.0,pyserial==3.5,pytest==7.2.1,pytest-cov==3.0.0,readline==6.2.4.1,scour==0.37,six==1.16.0,tomli==2.0.1,tox==3.28.0,typing_extensions==4.5.0,virtualenv==20.19.0,zstandard==0.20.0
py39 run-test-pre: PYTHONHASHSEED='1228586876'
py39 run-test: commands[0] | pytest tests/test_pyparsing.py
============================ test session starts =============================
platform linux -- Python 3.9.12[pypy-7.3.9-final], pytest-7.2.1, pluggy-1.0.0
cachedir: .tox/py39/.pytest_cache
rootdir: /home/sabin/extension-ai, configfile: pyproject.toml
plugins: cov-3.0.0
collected 10 items                                                           

tests/test_pyparsing.py .F....F..F

================================== FAILURES ==================================
_________________________ test_with_parse_action[0] __________________________

value = ''

    @pytest.mark.parametrize("value",
        [
            "x",
            "",  # removes named value
            True,
            False,
            1,
            0,
            None, # consistent with test_no_parse_action
            b"",
            b"a",
        ]
    )
    def test_with_parse_action(value):
        """Check which values are in the named result."""
        print("value =", repr(value))
        grammar = (pp.Suppress("a") + pp.ZeroOrMore("x")).add_parse_action(lambda p: value).set_results_name("X")
>       assert_X_is_present(grammar, value)

tests/test_pyparsing.py:30: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

grammar = {Suppress:('a') ['x']...}, X = ''

    def assert_X_is_present(grammar, X):
        """Check that X is present and has a certain value."""
        parse_results = grammar.parse_string("a")
        print(parse_results, list(parse_results.items()))
>       assert "X" in parse_results, f"value = {repr(X)}"
E       AssertionError: value = ''
E       assert 'X' in ParseResults([''], {})

tests/test_pyparsing.py:10: AssertionError
---------------------------- Captured stdout call ----------------------------
value = ''
[''] []
________________________ test_with_parse_action[None] ________________________

value = None

    @pytest.mark.parametrize("value",
        [
            "x",
            "",  # removes named value
            True,
            False,
            1,
            0,
            None, # consistent with test_no_parse_action
            b"",
            b"a",
        ]
    )
    def test_with_parse_action(value):
        """Check which values are in the named result."""
        print("value =", repr(value))
        grammar = (pp.Suppress("a") + pp.ZeroOrMore("x")).add_parse_action(lambda p: value).set_results_name("X")
>       assert_X_is_present(grammar, value)

tests/test_pyparsing.py:30: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

grammar = {Suppress:('a') ['x']...}, X = None

    def assert_X_is_present(grammar, X):
        """Check that X is present and has a certain value."""
        parse_results = grammar.parse_string("a")
        print(parse_results, list(parse_results.items()))
        assert "X" in parse_results, f"value = {repr(X)}"
>       assert parse_results["X"] == X
E       assert ParseResults([], {}) == None

tests/test_pyparsing.py:11: AssertionError
---------------------------- Captured stdout call ----------------------------
value = None
[] [('X', ParseResults([], {}))]
____________________________ test_no_parse_action ____________________________

    def test_no_parse_action():
        """Do not add a parse result."""
        grammar = (pp.Suppress("a") + pp.ZeroOrMore("x")).set_results_name("X")
>       assert_X_is_present(grammar, "")

tests/test_pyparsing.py:36: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

grammar = {Suppress:('a') ['x']...}, X = ''

    def assert_X_is_present(grammar, X):
        """Check that X is present and has a certain value."""
        parse_results = grammar.parse_string("a")
        print(parse_results, list(parse_results.items()))
        assert "X" in parse_results, f"value = {repr(X)}"
>       assert parse_results["X"] == X
E       AssertionError: assert ParseResults([], {}) == ''

tests/test_pyparsing.py:11: AssertionError
---------------------------- Captured stdout call ----------------------------
[] [('X', ParseResults([], {}))]
============================== warnings summary ==============================
<frozen importlib._bootstrap>:228
  <frozen importlib._bootstrap>:228: ImportWarning: can't resolve package from __spec__ or __package__, falling back on __name__ and __path__

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
========================== short test summary info ===========================
FAILED tests/test_pyparsing.py::test_with_parse_action[0] - AssertionError: value = ''
FAILED tests/test_pyparsing.py::test_with_parse_action[None] - assert ParseResults([], {}) == None
FAILED tests/test_pyparsing.py::test_no_parse_action - AssertionError: assert ParseResults([], {}) == ''
=================== 3 failed, 7 passed, 1 warning in 0.34s ===================
ERROR: InvocationError for command /home/sabin/extension-ai/.tox/py39/bin/pytest tests/test_pyparsing.py (exited with code 1)
__________________________________ summary ___________________________________
ERROR:   py39: commands failed

I wonder: It looks like an inconsistency but it could also be intentional. I thought, I will better get in contact before I start digging in too much. If this is a bug, I am happy to work on it.

Looking at this this afternoon.

ParseResults will not add a "nullish" value, like [], () or "". But we all know that "" is rarely an empty sequence like [] or () are, but very often a legitimate string value. If I remove "" from the list of null values that ParseResults drops on the floor, all of my unit tests still pass. This may be because this is a benign change, or because my unit tests are not very good. Since I am looking at dropping a2 release, this might be a decent time to try this change, before 3.1.0 becomes official.