cdfmlr / pyflowchart

Python codes to Flowcharts
https://pypi.org/project/pyflowchart/
MIT License
358 stars 72 forks source link

Support for match-case statement: AttributeError: 'Unparser' object has no attribute '_Match' #28

Open bjoernma opened 1 year ago

bjoernma commented 1 year ago

As pyflowchart 0.3.1 used with python 3.11 and astunparse 1.6.3 throws an exception: Traceback (most recent call last): File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main return _run_code(code, main_globals, None, File "/usr/lib/python3.10/runpy.py", line 86, in _run_code exec(code, run_globals) File "/home/bjorn/.local/lib/python3.10/site-packages/pyflowchart/__main__.py", line 115, in <module> main(args.code_file, args.field, args.inner, args.output, args.no_simplify, args.conds_align) File "/home/bjorn/.local/lib/python3.10/site-packages/pyflowchart/__main__.py", line 85, in main flowchart = Flowchart.from_code(code, File "/home/bjorn/.local/lib/python3.10/site-packages/pyflowchart/flowchart.py", line 102, in from_code p = parse(f, simplify=simplify, conds_align=conds_align) File "/home/bjorn/.local/lib/python3.10/site-packages/pyflowchart/ast_node.py", line 659, in parse node = ast_node_class(ast_object, **kwargs) File "/home/bjorn/.local/lib/python3.10/site-packages/pyflowchart/ast_node.py", line 137, in __init__ self.body_head, self.body_tails = self.parse_func_body(**kwargs) File "/home/bjorn/.local/lib/python3.10/site-packages/pyflowchart/ast_node.py", line 158, in parse_func_body p = parse(self.ast_object.body, **kwargs) File "/home/bjorn/.local/lib/python3.10/site-packages/pyflowchart/ast_node.py", line 659, in parse node = ast_node_class(ast_object, **kwargs) File "/home/bjorn/.local/lib/python3.10/site-packages/pyflowchart/ast_node.py", line 463, in __init__ OperationNode.__init__(self, operation=self.ast_to_source()) File "/home/bjorn/.local/lib/python3.10/site-packages/pyflowchart/ast_node.py", line 33, in ast_to_source return astunparse.unparse(self.ast_object).strip() File "/home/bjorn/.local/lib/python3.10/site-packages/astunparse/__init__.py", line 13, in unparse Unparser(tree, file=v) File "/home/bjorn/.local/lib/python3.10/site-packages/astunparse/unparser.py", line 38, in __init__ self.dispatch(tree) File "/home/bjorn/.local/lib/python3.10/site-packages/astunparse/unparser.py", line 65, in dispatch meth = getattr(self, "_"+tree.__class__.__name__) AttributeError: 'Unparser' object has no attribute '_Match'

I looked into a solution. It now seems that replacing astunparse with the inbuild package ast package (see e.g. https://github.com/simonpercivall/astunparse/issues/56#issuecomment-1438353347) doesnt throw this and works atm.

cdfmlr commented 1 year ago

Thanks for reporting. I'm on it!

cdfmlr commented 1 year ago

@bjoernma would you mind to provide some more detailed information about how you triggered the exception. I think I've fixed it (by following the solution you mentioned), but I haven't been able to reproduce the problem to test it. So I am not sure if there are any further bugs that prevent you from using PyFlowchart freely.

AzureArmageddon commented 1 year ago

Able to replicate using python 3.10.11, pyflowchart 0.3.1, and astunparse 1.6.3 on a new Repl on Replit. Happened after adding a match-case block to my code. Traceback generated follows:

Traceback (most recent call last):
  File "/nix/store/xf54733x4chbawkh1qvy9i1i4mlscy1c-python3-3.10.11/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/nix/store/xf54733x4chbawkh1qvy9i1i4mlscy1c-python3-3.10.11/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/runner/Hollow-Knight-Charm-Build-Randomiser/.pythonlibs/lib/python3.10/site-packages/pyflowchart/__main__.py", line 115, in <module>
    main(args.code_file, args.field, args.inner, args.output, args.no_simplify, args.conds_align)
  File "/home/runner/Hollow-Knight-Charm-Build-Randomiser/.pythonlibs/lib/python3.10/site-packages/pyflowchart/__main__.py", line 85, in main
    flowchart = Flowchart.from_code(code,
  File "/home/runner/Hollow-Knight-Charm-Build-Randomiser/.pythonlibs/lib/python3.10/site-packages/pyflowchart/flowchart.py", line 102, in from_code
    p = parse(f, simplify=simplify, conds_align=conds_align)
  File "/home/runner/Hollow-Knight-Charm-Build-Randomiser/.pythonlibs/lib/python3.10/site-packages/pyflowchart/ast_node.py", line 659, in parse
    node = ast_node_class(ast_object, **kwargs)
  File "/home/runner/Hollow-Knight-Charm-Build-Randomiser/.pythonlibs/lib/python3.10/site-packages/pyflowchart/ast_node.py", line 463, in __init__
    OperationNode.__init__(self, operation=self.ast_to_source())
  File "/home/runner/Hollow-Knight-Charm-Build-Randomiser/.pythonlibs/lib/python3.10/site-packages/pyflowchart/ast_node.py", line 33, in ast_to_source
    return astunparse.unparse(self.ast_object).strip()
  File "/home/runner/Hollow-Knight-Charm-Build-Randomiser/.pythonlibs/lib/python3.10/site-packages/astunparse/__init__.py", line 13, in unparse
    Unparser(tree, file=v)
  File "/home/runner/Hollow-Knight-Charm-Build-Randomiser/.pythonlibs/lib/python3.10/site-packages/astunparse/unparser.py", line 38, in __init__
    self.dispatch(tree)
  File "/home/runner/Hollow-Knight-Charm-Build-Randomiser/.pythonlibs/lib/python3.10/site-packages/astunparse/unparser.py", line 65, in dispatch
    meth = getattr(self, "_"+tree.__class__.__name__)
AttributeError: 'Unparser' object has no attribute '_Match'
cdfmlr commented 1 year ago

Thanks, @AzureArmageddon. There seem to be more jobs to do aside the compatibility of astunparse package to support the new match statement. I am considering literal convert the match-case back into the if-else statement OR a new kind of AstNode would be required to support it.

Since a multi-branch condition node is not supported by flowchart.js (See https://github.com/cdfmlr/pyflowchart/issues/13 for details), even though we have introduced a conds-align feature which may be useful, supporting for match could be really hard. 😭

cdfmlr commented 1 year ago

As the match-case statement has been widely adopted today, I recommend that those encountering this problem consider a quick fix:

cdfmlr commented 1 year ago

Progress:

With my latest commit https://github.com/cdfmlr/pyflowchart/commit/12fcc03b1adc078c0053689e98008873cf19228b, PyFlowchart is now able to read & parse the match sentences (with Python 3.10+).

As mentioned before,

Since a multi-branch condition node is not supported by flowchart.js (See #13 for details), even though we have introduced a conds-align feature which may be useful, supporting for match could be really hard. 😭

I have no choice but to make each case in the match a condition node (looks like a if without else) and connect them one by one.

For example:

def test_match(a, b, c):
    if a > 0:
        match b:
            case 1:
                print('ab')
            case 2:
                print('abc')
                c = 1 + b + a
            case 3:
                print('nested match')
                match c:
                    case["a"]:
                        print('a')
                    case["a", *other_items]:
                        print('a and others')
                    case[*first_items, "d"] | (*first_items, "d"):
                        print('d is the last item')
            case _:
                print('abcd')
        end_of_match()
    else:
        alez()
    end_of_ifs()

Excuse me, this code as a example is a bit verbose (but it covers common use cases, I assume). So the generated flowchart is "a bit" long:

match-case

It's definitely not ideal. I have tried to make it more readable (by reusing the conds-align and connection direction features), but it backfired on me:

failed to align match-cases

No one like this bad-routed maze. So I decide to remove all this programic "beautify" features. Just leave it "a bit long".

AzureArmageddon commented 1 year ago

So, in short, it would appear that astunparse's inability to parse match-case has been overcome, but flowchart.js is unable to handle multi-branching conditionals in the exact way we want so this temporary implementation must be used. Not ideal, but that means there is officially a new feature working! 🎉

Perhaps making an issue on flowchart.js would be appropriate. I don't suppose we can close this issue until flowchart.js makes changes (or mayhaps if an attractive alternative comes along as a viable replacement)?

cdfmlr commented 1 year ago

Perhaps making an issue on flowchart.js would be appropriate.

There's an existing one that has persisted for years: https://github.com/adrai/flowchart.js/issues/60.

or mayhaps if an attractive alternative comes along as a viable replacement

This is exactly what I am trying to.

I am considering mermaid.js as a potential alternative. Lately, I've favored mermaid.js over flowchart.js. However, it's not a straightforward shift due to PyFlowchart's initial design being closely tied to flowchart.js.

Adopting mermaid.js will undoubtedly cause significant disruptions to our stable compatibility, which I take pride in since its initial release in 2020 for Python 3.6/3.7.

So I'm still seeking a way to make a refactor that not destroy the current flowchart.js features, but allow us to add a support for mermaid.js. (Maybe the silver bullet here is the visitor pattern?)

cdfmlr commented 1 year ago

Anyway, I'm releasing a preview (not well tested) version v0.4.0-alpha.4 that contains current progress for match-case support. pip install pyflowchart==0.4.0a4 to use it.

Keep this issue open for seeking further enhancements.

kaasima10 commented 5 months ago

As pyflowchart 0.3.1 used with python 3.11 and astunparse 1.6.3 throws an exception:

Traceback (most recent call last): File "C:\Downloads\sc-navigator-model-generation-cp\APO Input Workflow\src\flowchart.py", line 12, in fc = pfc.parse(code) ^^^^^^^^^^^^^^^ File "C:\Downloads\sc-navigator-model-generation-cp\APO Input Workflow\src\new_env\Lib\site-packages\pyflowchart\ast_node.py", line 659, in parse node = ast_node_class(ast_object, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Downloads\sc-navigator-model-generation-cp\APO Input Workflow\src\new_env\Lib\site-packages\pyflowchart\ast_node.py", line 463, in init OperationNode.init(self, operation=self.ast_to_source()) ^^^^^^^^^^^^^^^^^^^^ File "C:\Downloads\sc-navigator-model-generation-cp\APO Input Workflow\src\new_env\Lib\site-packages\pyflowchart\ast_node.py", line 33, in ast_to_source return astunparse.unparse(self.ast_object).strip() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Downloads\sc-navigator-model-generation-cp\APO Input Workflow\src\new_env\Lib\site-packages\astunparse__init__.py", line 13, in unparse Unparser(tree, file=v) File "C:\Downloads\sc-navigator-model-generation-cp\APO Input Workflow\src\new_env\Lib\site-packages\astunparse\unparser.py", line 38, in init self.dispatch(tree) File "C:\Downloads\sc-navigator-model-generation-cp\APO Input Workflow\src\newenv\Lib\site-packages\astunparse\unparser.py", line 65, in dispatch meth = getattr(self, ""+tree.class.name) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'Unparser' object has no attribute '_str'. Did you mean: '_Str'?

How do I resolve this issue?

cdfmlr commented 5 months ago

@kaasima10, it appears that your comment may not be related to this thread. Your issue will be traced in #32.