From 0f9ced15287d1c705bdde54c47b43e602b10e810 Mon Sep 17 00:00:00 2001 From: Joel Flores Date: Fri, 7 Mar 2025 16:44:23 -0600 Subject: [PATCH] initial commit --- .gitignore | 18 + LICENSE | 674 ++ README.md | 60 + demo.gif | Bin 0 -> 725246 bytes model_training/README.md | 48 + .../dataset_generation/DatasetDescription.py | 50 + model_training/dataset_generation/__init__.py | 3 + .../dataset_generation/bytecode2csv.py | 216 + .../dataset_generation/create_code_dataset.py | 114 + .../dataset_generation/normalize_source.py | 67 + .../dataset_generation/upload_raw_dataset.py | 60 + model_training/prepare_dataset.py | 66 + .../sample_jsons/py36-sample-data.json | 24 + .../py36-sample-segmentation.json | 18 + .../sample_jsons/py36-sample-statement.json | 16 + .../segmentation/SegmentationConfiguration.py | 74 + model_training/segmentation/tokenize_seg.py | 152 + model_training/segmentation/train_mlm.py | 195 + model_training/segmentation/train_seg.py | 155 + .../segmentation/train_tokenizer.py | 96 + model_training/statement/README.md | 18 + .../statement/StatementConfiguration.py | 59 + model_training/statement/__init__.py | 0 model_training/statement/tokenize_seq2seq.py | 51 + .../tokenizer/special_tokens_map.json | 5471 ++++++++++++ .../statement/tokenizer/tokenizer.json | 7775 +++++++++++++++++ .../statement/tokenizer/tokenizer_config.json | 929 ++ model_training/statement/train_seq2seq.py | 93 + .../statement/train_tokenizer_auto.py | 93 + model_training/train_models.py | 117 + poetry.lock | 3044 +++++++ pylingual/__init__.py | 3 + .../control_flow_reconstruction/cfg_utils.py | 58 + .../control_flow_reconstruction/cflow.py | 57 + .../control_flow_templates/Subgraph.py | 171 + .../AbstractExceptionBlockTemplate.py | 5 + .../AbstractNonSequentiableTemplate.py | 5 + .../abstract/AbstractTemplate.py | 42 + .../booleans/ChainedComparisonTemplate.py | 321 + .../booleans/ShortCircuitAndTemplate.py | 274 + .../ShortCircuitOrContinueTemplate.py | 188 + .../booleans/ShortCircuitOrTemplate.py | 157 + .../context_managers/AsyncWithCleanup312.py | 130 + .../context_managers/Await312Template.py | 136 + .../context_managers/WithCleanup312.py | 128 + .../context_managers/WithTemplate.py | 177 + .../context_managers/WithTemplate312.py | 124 + .../context_managers/WithTemplate39.py | 115 + .../deprecated/ElseExitExceptTemplate.py | 180 + .../deprecated/ElseExitTemplate.py | 144 + .../deprecated/ExceptExitTemplate.py | 148 + .../deprecated/IfElseExitTemplate.py | 185 + .../deprecated/IfExitExceptTemplate.py | 162 + .../deprecated/IfExitTemplate.py | 133 + .../deprecated/TryExitExceptExitTemplate.py | 139 + .../deprecated/TryExitTemplate.py | 153 + .../if_then/ConditionalExitTemplate.py | 115 + .../if_then/IfElseTemplate.py | 194 + .../if_then/IfThenJumpTemplate.py | 162 + .../if_then/IfThenTemplate.py | 173 + .../loop/AsyncForTemplate.py | 152 + .../loop/ForIf312Template.py | 120 + .../loop/InlinedComprehension.py | 97 + .../loop/LoopExitTemplate.py | 54 + .../loop/LoopTemplate.py | 143 + .../loop/PreRefinedLoopTemplate.py | 57 + .../loop/RefinedLoopTemplate.py | 147 + .../loop/SelfLoopTemplate.py | 85 + .../loop/WhileTrueIfElseTemplate.py | 135 + .../control_flow_templates/match_utils.py | 232 + .../natural/InstructionTemplate.py | 69 + .../natural/LineTemplate.py | 37 + .../natural/LinearSequenceTemplate.py | 127 + .../placeholders/ExceptPlaceholderTemplate.py | 30 + .../IrreduciblePlaceholderTemplate.py | 9 + .../WhileTruePlaceholderTemplate.py | 42 + .../subtemplates/OptionalExitSubtemplate.py | 130 + .../try_except/ExceptAsCleanup.py | 139 + .../try_except/ExceptAsExceptTemplate.py | 154 + .../try_except/ExceptAsExitTemplate.py | 159 + .../try_except/ExceptAsTemplate.py | 141 + .../try_except/ExceptException.py | 120 + .../try_except/FinallyTemplate.py | 167 + .../try_except/GeneratorCleanupTemplate.py | 70 + .../try_except/TryExceptElseTemplate.py | 167 + .../try_except/TryExceptTemplate.py | 188 + .../try_except/TryFinallyTemplate.py | 173 + .../post_311/ExceptAsCleanupSubTemplate311.py | 159 + .../ExceptAsNonMatchSubtemplate311.py | 228 + .../post_311/ExceptAsTemplate311.py | 196 + .../try_except/post_311/ExceptTemplate311.py | 430 + .../try_except/post_311/FinallyTemplate312.py | 243 + .../post_311/TryExceptTemplate311.py | 213 + .../try_except/post_311/TryTemplate311.py | 178 + .../try_except/post_311/TryTemplate312.py | 169 + .../try_except/pre_39/ExceptAsPre39.py | 165 + .../try_except/pre_39/TryFinallyExitPre39.py | 211 + .../try_except/pre_39/TryFinallyPre39.py | 188 + .../reconstruct_control_indentation.py | 114 + .../structure_control_flow.py | 452 + pylingual/decompiler.py | 499 ++ pylingual/decompiler_config.yaml | 87 + .../editable_bytecode/EditableBytecode.py | 860 ++ pylingual/editable_bytecode/Instruction.py | 266 + pylingual/editable_bytecode/PYCFile.py | 84 + pylingual/editable_bytecode/__init__.py | 7 + .../bytecode_patches/__init__.py | 5 + .../bytecode_patches/fix_indirect_jump.py | 12 + .../bytecode_patches/fix_unreachable.py | 7 + .../bytecode_patches/remove_docstrings.py | 9 + .../bytecode_patches/remove_extended_arg.py | 8 + .../bytecode_patches/remove_nop.py | 6 + .../editable_bytecode/control_flow_graph.py | 183 + pylingual/editable_bytecode/utils.py | 89 + pylingual/equivalence_check.py | 218 + pylingual/main.py | 164 + pylingual/masking/ast_masker.py | 327 + pylingual/masking/global_masker.py | 264 + pylingual/masking/model_disasm.py | 162 + pylingual/models.py | 131 + .../segmentation_search_strategies.py | 140 + pylingual/segmentation/sliding_window.py | 140 + pylingual/utils/ascii_art.py | 25 + pylingual/utils/generate_bytecode.py | 29 + pylingual/utils/get_logger.py | 30 + pylingual/utils/lazy.py | 17 + pylingual/utils/lists.py | 11 + pylingual/utils/tracked_list.py | 63 + pylingual/utils/use_escape_sequences.py | 17 + pylingual/utils/version.py | 75 + pyproject.toml | 96 + 131 files changed, 33856 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 demo.gif create mode 100644 model_training/README.md create mode 100644 model_training/dataset_generation/DatasetDescription.py create mode 100644 model_training/dataset_generation/__init__.py create mode 100644 model_training/dataset_generation/bytecode2csv.py create mode 100644 model_training/dataset_generation/create_code_dataset.py create mode 100644 model_training/dataset_generation/normalize_source.py create mode 100644 model_training/dataset_generation/upload_raw_dataset.py create mode 100644 model_training/prepare_dataset.py create mode 100644 model_training/sample_jsons/py36-sample-data.json create mode 100644 model_training/sample_jsons/py36-sample-segmentation.json create mode 100644 model_training/sample_jsons/py36-sample-statement.json create mode 100644 model_training/segmentation/SegmentationConfiguration.py create mode 100644 model_training/segmentation/tokenize_seg.py create mode 100644 model_training/segmentation/train_mlm.py create mode 100644 model_training/segmentation/train_seg.py create mode 100644 model_training/segmentation/train_tokenizer.py create mode 100644 model_training/statement/README.md create mode 100644 model_training/statement/StatementConfiguration.py create mode 100644 model_training/statement/__init__.py create mode 100644 model_training/statement/tokenize_seq2seq.py create mode 100644 model_training/statement/tokenizer/special_tokens_map.json create mode 100644 model_training/statement/tokenizer/tokenizer.json create mode 100644 model_training/statement/tokenizer/tokenizer_config.json create mode 100644 model_training/statement/train_seq2seq.py create mode 100644 model_training/statement/train_tokenizer_auto.py create mode 100644 model_training/train_models.py create mode 100644 poetry.lock create mode 100644 pylingual/__init__.py create mode 100644 pylingual/control_flow_reconstruction/cfg_utils.py create mode 100644 pylingual/control_flow_reconstruction/cflow.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/Subgraph.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractExceptionBlockTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractNonSequentiableTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/booleans/ChainedComparisonTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitAndTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitOrContinueTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitOrTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/context_managers/AsyncWithCleanup312.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/context_managers/Await312Template.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithCleanup312.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate312.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate39.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ElseExitExceptTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ElseExitTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ExceptExitTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfElseExitTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfExitExceptTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfExitTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/deprecated/TryExitExceptExitTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/deprecated/TryExitTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/if_then/ConditionalExitTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfElseTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfThenJumpTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfThenTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/loop/AsyncForTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/loop/ForIf312Template.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/loop/InlinedComprehension.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/loop/LoopExitTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/loop/LoopTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/loop/PreRefinedLoopTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/loop/RefinedLoopTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/loop/SelfLoopTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/loop/WhileTrueIfElseTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/match_utils.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/natural/InstructionTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/natural/LineTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/natural/LinearSequenceTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/placeholders/ExceptPlaceholderTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/placeholders/IrreduciblePlaceholderTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/placeholders/WhileTruePlaceholderTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/subtemplates/OptionalExitSubtemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsCleanup.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsExceptTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsExitTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptException.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/FinallyTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/GeneratorCleanupTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryExceptElseTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryExceptTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryFinallyTemplate.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsCleanupSubTemplate311.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsNonMatchSubtemplate311.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsTemplate311.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptTemplate311.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/FinallyTemplate312.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryExceptTemplate311.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryTemplate311.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryTemplate312.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/ExceptAsPre39.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/TryFinallyExitPre39.py create mode 100644 pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/TryFinallyPre39.py create mode 100644 pylingual/control_flow_reconstruction/reconstruct_control_indentation.py create mode 100644 pylingual/control_flow_reconstruction/structure_control_flow.py create mode 100644 pylingual/decompiler.py create mode 100644 pylingual/decompiler_config.yaml create mode 100644 pylingual/editable_bytecode/EditableBytecode.py create mode 100644 pylingual/editable_bytecode/Instruction.py create mode 100644 pylingual/editable_bytecode/PYCFile.py create mode 100644 pylingual/editable_bytecode/__init__.py create mode 100644 pylingual/editable_bytecode/bytecode_patches/__init__.py create mode 100644 pylingual/editable_bytecode/bytecode_patches/fix_indirect_jump.py create mode 100644 pylingual/editable_bytecode/bytecode_patches/fix_unreachable.py create mode 100644 pylingual/editable_bytecode/bytecode_patches/remove_docstrings.py create mode 100644 pylingual/editable_bytecode/bytecode_patches/remove_extended_arg.py create mode 100644 pylingual/editable_bytecode/bytecode_patches/remove_nop.py create mode 100644 pylingual/editable_bytecode/control_flow_graph.py create mode 100644 pylingual/editable_bytecode/utils.py create mode 100644 pylingual/equivalence_check.py create mode 100644 pylingual/main.py create mode 100644 pylingual/masking/ast_masker.py create mode 100644 pylingual/masking/global_masker.py create mode 100644 pylingual/masking/model_disasm.py create mode 100644 pylingual/models.py create mode 100644 pylingual/segmentation/segmentation_search_strategies.py create mode 100644 pylingual/segmentation/sliding_window.py create mode 100644 pylingual/utils/ascii_art.py create mode 100644 pylingual/utils/generate_bytecode.py create mode 100644 pylingual/utils/get_logger.py create mode 100644 pylingual/utils/lazy.py create mode 100644 pylingual/utils/lists.py create mode 100644 pylingual/utils/tracked_list.py create mode 100644 pylingual/utils/use_escape_sequences.py create mode 100644 pylingual/utils/version.py create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4eb6f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +dataset/ +venv/ +*.pyc +.idea/ +.python-version +poetry.toml +test/ +.DS_Store +results/ +.vscode/ +errors/ +logs/ +segmentation_test_cases/ +__pycache__/ +.env +mise.toml +dist/ +decompiled_*/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8460380 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# PyLingual - Python Decompiler for 3.6+ + +PyLingual is a CPython bytecode decompiler supporting all released Python versions since 3.6. For information about the design and implementation of PyLingual, please refer to our [research paper](https://www.computer.org/csdl/proceedings-article/sp/2025/223600a052/21B7QZB86cg). + +PyLingual can be run through our [web service](https://pylingual.io) or run locally. + +This codebase is optimized for readability and future extension, so there may initially be some control flow accuracy regression compared to the version hosted on the web service. + +## Requirements + +- Python 3.11 or higher + +### Compiling bytecode + +Some parts of PyLingual require the ability to compile bytecode in a different Python version (equivalence check and model training). For this, you will need the following: + +- [pyenv](https://github.com/pyenv/pyenv) with all Python versions you want to compile to +- Unix-like operating system (pyenv does not support Windows) + +## Setup + +Install from source, using [Poetry](https://python-poetry.org/): + +```sh +git clone https://github.com/syssec-utd/pylingual +cd pylingual +python -m venv venv +source venv/bin/activate +pip install poetry +poetry install +``` + +## Usage + +``` +Usage: pylingual [OPTIONS] [FILES]... + + End to end pipeline to decompile Python bytecode into source code. + +Options: + -o, --out-dir PATH The directory to export results to. + -c, --config-file PATH Config file for model information. + -v, --version VERSION Python version of the .pyc, default is auto + detection. + -k, --top-k INT Maximum number of additional segmentations to + consider. + -q, --quiet Suppress console output. + --trust-lnotab Use the lnotab for segmentation instead of the + segmentation model. + --init-pyenv Install pyenv before decompiling. + -h, --help Show this message and exit. +``` + +## Demo + +![demo gif](demo.gif) + +## Support + +If you have any issues for installing and using PyLingual, please create an issue or send your message via our support email at pylingual@gmail.com. diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc6a38dd11e6bc9ba0b121a7a4a646514e28f18a GIT binary patch literal 725246 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%0Ao(K_4ZcW!DjRSM7Dqm>zkY2&VzQ9N@ozk*x{ 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

OIkd6? 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 JFwt|Af^;o=jxV5Of{8NCCo^uT}gt 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 literal 0 HcmV?d00001 diff --git a/model_training/README.md b/model_training/README.md new file mode 100644 index 0000000..9cfc0ce --- /dev/null +++ b/model_training/README.md @@ -0,0 +1,48 @@ +# Model Training + +PyLingual's accuracy is dependent on having accurate segmentation and statement models [^1]. The segmentation model divides a list of bytecode instructions into groups for each source instruction. The statement model transforms each group of instructions into source code. The instructions for training these models is as follows: + +## Dataset generation + +First install [pyenv](https://github.com/pyenv/pyenv) and the required Python versions for the dataset. Create a dataset JSON file based off the sample (`sample_jsons/py36-sample-data.json`). + +The dataset directory should be structured like so, with only one `.py` file per directory: + +``` +dataset +├── 0 +│   └── file.py +├── 1 +│   └── file.py +... +├── 999 +│   └── file.py +└── 1000 + └── file.py +``` + +The names of the inner directories and files do not matter. Then create the dataset: + +``` +python prepare_dataset.py +``` + +## Segmentation model + +Create a segmentation model JSON file based off the sample (`sample_jsons/py36-sample-segmentation.json`). Then train the model: + +``` +python train_models.py --segmentation +``` + +## Statement model + +Create a statement model JSON file based off the sample (`sample_jsons/py36-sample-statement.json`). Then train the model: + +``` +python train_models.py --statement +``` + +Once models are trained, update `../pylingual/decompiler_config.yaml` or create a separate config file by replacing the old models with the newly trained ones. + +[^1]: [pylingual models](https://huggingface.co/syssec-utd). diff --git a/model_training/dataset_generation/DatasetDescription.py b/model_training/dataset_generation/DatasetDescription.py new file mode 100644 index 0000000..5513270 --- /dev/null +++ b/model_training/dataset_generation/DatasetDescription.py @@ -0,0 +1,50 @@ +from dataclasses import dataclass +import pathlib + +from typing import Tuple, List + + +@dataclass +class DataRequest: + name: str + source_path: pathlib.Path + num_train: int + num_test: int + num_valid: int + + @property + def total_files(self): + return self.num_train + self.num_test + self.num_valid + + def __post_init__(self): + self.source_path = pathlib.Path(self.source_path) + if not self.source_path.exists(): + raise FileNotFoundError(f"{self.source_path} for DataRequest {self.name} does not exist") + + if self.num_train < 0: + raise ValueError(f"Training sample count for DataRequest {self.name} must be non-negative") + if self.num_test < 0: + raise ValueError(f"Testing sample count for DataRequest {self.name} must be non-negative") + if self.num_valid < 0: + raise ValueError(f"Validation sample count for DataRequest {self.name} must be non-negative") + + +@dataclass +class DatasetDescription: + name: str + version: Tuple[int, int] + save_to_dir: pathlib.Path + huggingface_user: str + data_requests: List[DataRequest] + + @property + def code_dir(self): + return self.save_to_dir / self.name / "code" + + @property + def csv_dir(self): + return self.save_to_dir / self.name / "csv" + + def __post_init__(self): + self.save_to_dir = pathlib.Path(self.save_to_dir) + self.version = tuple(self.version) diff --git a/model_training/dataset_generation/__init__.py b/model_training/dataset_generation/__init__.py new file mode 100644 index 0000000..af9b642 --- /dev/null +++ b/model_training/dataset_generation/__init__.py @@ -0,0 +1,3 @@ +from .create_code_dataset import transfer_and_compile_file + +__all__ = ["transfer_and_compile_file"] diff --git a/model_training/dataset_generation/bytecode2csv.py b/model_training/dataset_generation/bytecode2csv.py new file mode 100644 index 0000000..130751b --- /dev/null +++ b/model_training/dataset_generation/bytecode2csv.py @@ -0,0 +1,216 @@ +import csv +import itertools +import logging +import multiprocessing +import pathlib +import re +import signal +from typing import Callable, Tuple + +import tqdm +from pylingual.editable_bytecode import PYCFile + +from pylingual.masking.ast_masker import DUMMY_DECORATOR +from pylingual.masking.model_disasm import fix_jump_targets +from .DatasetDescription import DataRequest +from pylingual.masking.model_disasm import create_global_masker, mask_source + +bytecode_separator = " " +source_seperator = " " +CSV_SGMT_HEADER = ["source", "bytecode", "boundary", "file"] +CSV_STMT_HEADER = ["source", "bytecode", "file"] + + +def create_csv_dataset(code_dataset_path: pathlib.Path, csv_dataset_path: pathlib.Path, data_requests: list[DataRequest], logger: logging.Logger = None): + progress_bar = tqdm.tqdm(total=sum([request.total_files for request in data_requests])) + for split in ("train", "test", "valid"): + if logger: + logger.info(f"Converting the {split} split to CSV...") + write_csvs(code_dataset_path / split, csv_dataset_path / split, logger, progress_bar=progress_bar) + + +def write_csvs(source_path: pathlib.Path, csv_output_path: pathlib.Path, logger: logging.Logger = None, max_csv_rows: int = 30000, progress_bar: tqdm.tqdm = None): + # validate output directory + if csv_output_path.exists(): + if not csv_output_path.is_dir(): + raise OSError("CSV output path is not a directory") + else: + csv_output_path.mkdir(parents=True) + + ##### csv write wrappers to preserve csv row limit + + def csv_writer(file_prefix: str, csv_header: list) -> Callable: + out_dir = csv_output_path.joinpath(file_prefix) + out_dir.mkdir(exist_ok=True) + + for csv_idx in itertools.count(): + new_path = out_dir.joinpath(f"{file_prefix}_{csv_idx}.csv") + new_path.touch() + if logger: + logger.info(f"Creating new csv {new_path.resolve()}...") + with new_path.open(mode="w") as csv_file: + writer = csv.writer(csv_file) + writer.writerow(csv_header) + for writer in itertools.repeat(writer, max_csv_rows): + yield writer.writerow + + segmentation_writer = csv_writer("segmentation", CSV_SGMT_HEADER) + statement_writer = csv_writer("statement", CSV_STMT_HEADER) + + # create dirs + code_dirs = (child for child in source_path.iterdir() if child.is_dir()) + + def bytecode2csv_args(): + for dir in code_dirs: + py_path = next(dir.glob("*.py"), None) + pyc_path = next(dir.glob("*.pyc"), None) + if None in (py_path, pyc_path): + logging.debug(f"PY or PYC file not found in {dir}") + continue + else: + yield (py_path, pyc_path) + + num_fails = 0 + with multiprocessing.Pool() as pool: + for result in pool.imap_unordered(bytecode2csv_exception_wrapper, bytecode2csv_args()): + if isinstance(result, Exception): + num_fails += 1 + logger.debug(f"DIR: {dir}\nERR: {result}\nTYPE ERR: {type(result)}\n") + continue + + (segmentation_rows, statement_rows) = result + for row, writerow in zip(segmentation_rows, segmentation_writer): + writerow(row) + for row, writerow in zip(statement_rows, statement_writer): + writerow(row) + + if progress_bar: + progress_bar.update() + progress_bar.set_postfix({"num_fails": num_fails}) + + logger.info(f"NUMBER OF FAILS !!! {num_fails}") + + +def timeout_handler(signum, frame): + raise TimeoutError() + + +def bytecode2csv_exception_wrapper(paths=Tuple[pathlib.Path, pathlib.Path]) -> Tuple[list, list] | Exception: + signal.signal(signal.SIGALRM, timeout_handler) + try: + signal.alarm(30) # set 30 second timeout + results = bytecode2csv(*paths) + signal.alarm(0) # success; disable timer + return results + except Exception as error: + signal.alarm(0) # disable timer in case another exception triggered the fail + return Exception(f"{type(error)}: {error} in file {paths}") + + +def bytecode2csv(py_path: pathlib.Path, pyc_path: pathlib.Path) -> tuple[list, list]: + """Creates segmentation and statement csv rows for given bytecode and source file""" + segmentation_rows = [] + statement_rows = [] + + pyc = PYCFile(str(pyc_path.resolve())) + if pyc.version == (3, 10): + pyc.replace_duplicated_returns10(py_path.read_text().split("\n")) + elif pyc.version == (3, 12): + pyc.replace_duplicated_returns12(py_path.read_text().split("\n")) + global_masker = create_global_masker(pyc) + + masked_source_text = mask_source(py_path, global_masker, pyc.version) + masked_source_lines = masked_source_text.split("\n") + + # filter out dummy decorators added in <= 3.7 + dummy_lnos = [] + if pyc.version <= (3, 7): + # remove dummy decorators from bytecode' + pyc._patch_dummy_decorator(dummy_decorator_name=DUMMY_DECORATOR) + try: # if no functions are in source, then dummy will not exist + dummy_decorator_line = f"@{global_masker.mask(DUMMY_DECORATOR)}" + except KeyError: + dummy_decorator_line = None + dummy_lnos = [lno + 1 for lno, source in enumerate(masked_source_lines) if source.strip() == dummy_decorator_line] + + seen_lines = set() + + # create rows for each bytecode + for bc in pyc.iter_bytecodes(): + # we ignore comprehensions, hoisted later + if bc.is_comprehension: + continue + + # attempt to filter lines + lno_insts = bc.get_lno_insts(previously_seen_lines=seen_lines) + + # create line num : model disasm view of insts + lno_model_view_insts = {lno: [global_masker.get_model_view(inst) for inst in line_insts] for lno, line_insts in lno_insts.items()} + seen_lines.update(lno_model_view_insts.keys()) + + # segment source + if pyc.version <= (3, 7): + segmented_source_lines = [] + for line_num in lno_model_view_insts: + if not line_num: + segmented_source_lines.append("") + elif line_num in dummy_lnos: + segmented_source_lines.append(masked_source_lines[line_num].strip()) + else: + segmented_source_lines.append(masked_source_lines[line_num - 1].strip()) + else: + segmented_source_lines = [masked_source_lines[line_num - 1].strip() if line_num else "" for line_num in lno_model_view_insts.keys()] # -1 to convert from line num to index in array + + model_disasm_text = bytecode_separator.join(val for val in itertools.chain(*lno_model_view_insts.values())) + + if len(segmented_source_lines) != len(lno_model_view_insts): + raise ValueError("Length mismatch between segmented source and segmented bytecodes") + + # create bytecode segmentation + boundaries = [] + for bc_line in lno_model_view_insts.values(): + if len(bc_line) == 1: + bounds = "B" + elif len(bc_line) >= 2: + bounds = "B" + "I" * (len(bc_line) - 2) + "E" + else: + raise ValueError("Unexpected amount of bytecodes segmented into a line") + boundaries.extend(list(bounds)) + + # append rows + segmentation_rows.append([source_seperator.join(segmented_source_lines), model_disasm_text, boundaries, str(py_path)]) + for segmented_source, bytecodes in zip(segmented_source_lines, lno_model_view_insts.values()): + # skip empty lines + if not segmented_source or segmented_source == "None": + continue + # skip fillers + if segmented_source in ("pass", "...") and ("RETURN_VALUE" in bytecodes or "RETURN_CONST , None" in bytecodes): + continue + # skip string-only lines that aren't docstrings + if (segmented_source.startswith("'") or segmented_source.startswith('"')) and not any("__doc__" in b for b in bytecodes): + continue + if segmented_source.startswith("elif "): + segmented_source = segmented_source[2:] + + joined_bytecode = bytecode_separator.join(bytecodes) + + # DUCT-TAPE; skip samples where model has to guess masks + source_masks = set(re.findall(r"", segmented_source)) + bytecode_masks = set(re.findall(r"", joined_bytecode)) + if not source_masks <= bytecode_masks: + continue + + # normalize source mask order for statements + # replace mask values to start at 0 and count up + mask_regex = re.compile(r"(?<=)") + masks = mask_regex.findall(joined_bytecode) + mask_order = [x for i, x in enumerate(masks) if masks.index(x) == i] + normalized_mask_bytecode = mask_regex.sub(lambda x: str(mask_order.index(x.group(0))), joined_bytecode) + normalized_mask_source = mask_regex.sub(lambda x: str(mask_order.index(x.group(0))), segmented_source) + + # normalize jump targets + normalized_mask_bytecode = fix_jump_targets(normalized_mask_bytecode) + + statement_rows.append([normalized_mask_source, normalized_mask_bytecode, str(py_path)]) + + return (segmentation_rows, statement_rows) diff --git a/model_training/dataset_generation/create_code_dataset.py b/model_training/dataset_generation/create_code_dataset.py new file mode 100644 index 0000000..1afc03b --- /dev/null +++ b/model_training/dataset_generation/create_code_dataset.py @@ -0,0 +1,114 @@ +import itertools +import logging +import multiprocessing +import pathlib +import random +from typing import List, Optional, Set, Tuple + +import tqdm + +from .DatasetDescription import DataRequest +from pylingual.utils.generate_bytecode import compile_version +from .normalize_source import normalize_source +from pylingual.masking.ast_masker import add_dummy_decorators + + +def transfer_and_compile_file( + original_file: pathlib.Path, + destination_file: pathlib.Path, + version: Tuple[int, int], +) -> Optional[Exception]: + # copy over normalized source file + try: + normalized_source = normalize_source(original_file.read_text(), version=version, replace_docstrings=True) + + if version[:2] <= (3, 7): + normalized_source = add_dummy_decorators(normalized_source) + except Exception as err: + return err + + destination_file.parent.mkdir(parents=True, exist_ok=True) + destination_file.write_text(normalized_source) + + # compile the copied file with the given version + try: + compile_version( + destination_file.resolve(), + destination_file.with_suffix(".pyc").resolve(), + version, + ) + except Exception as err: + return err + + return None + + +def star_transfer_and_compile_file(args) -> Optional[Exception]: + return transfer_and_compile_file(*args) + + +# samples num_files files from the given directory +# expects the directory to have the structure +# source_dir -> identifier -> file.py +def sample_directory_splits( + data_request: DataRequest, +) -> Tuple[List[pathlib.Path], List[pathlib.Path], List[pathlib.Path]]: + all_files: Set[pathlib.Path] = set() + for identifier in data_request.source_path.iterdir(): + source_file = next(identifier.glob("*.py"), None) # get the first python file from the identifier + if source_file is not None: + all_files.add(source_file) + + # sample batches until we have enough files to satisfy the data requests + # this avoids running expensive tests on unsampled files + clean_sample: Set[pathlib.Path] = set() + while len(clean_sample) < data_request.total_files: + remaining_files = data_request.total_files - len(clean_sample) + sample_batch = random.sample(list(all_files), k=remaining_files) + # add the acceptable files to the sample and remove them from the population + to_add = set(candidate for candidate in sample_batch if candidate is not None) + clean_sample.update(to_add) + all_files -= to_add + + full_sample = iter(clean_sample) + + train = list(itertools.islice(full_sample, data_request.num_train)) + test = list(itertools.islice(full_sample, data_request.num_test)) + valid = list(itertools.islice(full_sample, data_request.num_valid)) + + return train, test, valid + + +def prepare_single_directory_transfer_args(data_request: DataRequest, target_dir: pathlib.Path) -> List[Tuple[pathlib.Path, pathlib.Path]]: + train, test, valid = sample_directory_splits(data_request) + + transfer_args = [] + for split_name, split_files in zip(("train", "test", "valid"), (train, test, valid)): + for source_file in split_files: + target_file = target_dir / split_name / f"{data_request.name}-{source_file.parent.name}" / source_file.name + transfer_args.append((source_file, target_file)) + + return transfer_args + + +# takes a dict of {: (num_train, num_test, num_valid)} and a target directory +# makes train, test, and split directories in the target directory with the normalized source files +def create_code_dataset( + data_requests: List[DataRequest], + target_dir: pathlib.Path, + version: Tuple[int, int], + logger: logging.Logger, +): + with multiprocessing.Pool() as pool: + # prepare a list of file transfers to execute + logger.info(f"Sampling {', '.join(str(req.source_path.resolve()) for req in data_requests)}...") + transfer_arg_lists = pool.starmap( + prepare_single_directory_transfer_args, + zip(data_requests, itertools.repeat(target_dir)), + ) + # execute the file transfers + versioned_transfer_arg_lists = [(source_file, target_file, version) for (source_file, target_file) in itertools.chain(*transfer_arg_lists)] + logger.info(f"Normalizing and Compiling {len(versioned_transfer_arg_lists)} files...") + for error in tqdm.tqdm(pool.imap_unordered(star_transfer_and_compile_file, versioned_transfer_arg_lists), total=len(versioned_transfer_arg_lists)): + if error is not None: + logger.debug(error) diff --git a/model_training/dataset_generation/normalize_source.py b/model_training/dataset_generation/normalize_source.py new file mode 100644 index 0000000..f9b684c --- /dev/null +++ b/model_training/dataset_generation/normalize_source.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +import ast +import pathlib +import sys +from typing import Tuple + + +def version_str_to_tuple(version_str: str) -> tuple[int, int]: + # a version string is a string like 3.9.2 + versions = [int(version) for version in version_str.split(".")] + return tuple(versions[:2]) + + +# must be run in python 3.9 or later for ast.unparse() support +# version defaults to whatever version this script is running in; needs to be set explicitly for backwards compatibility +# ast only supports versions 3.4 and later +def normalize_source( + source: str, + version: Tuple[int, int] = sys.version_info[0:2], + replace_docstrings=False, +) -> str: + """ + Parse the source code into an AST, then convert back to source. + This has the following normalizing effects: + 1. whitespace is set according to the PEP standard + 2. each statement is on exactly one line + 3. # comments are removed (note: docstrings are not removed) + + :param str source: The source code to normalize + :param tuple version: The (Major, Minor) version of python to parse with; must be at least (3, 4); defaults to + same version as this script + :param bool replace_docstrings: Replace all docstrings with 'pass' + """ + tree = ast.parse(source, feature_version=version) + if replace_docstrings: + for node in ast.walk(tree): + if isinstance(node, ast.Expr) and isinstance(node.value, ast.Str): + node.value.s = "pass" + return ast.unparse(tree) + + +def normalize_source_file( + source_file_path: str, + cleaned_suffix: str = "-cleaned", + version: tuple[int, int] = sys.version_info[0:2], +): + """ + Normalizes the source code in a given file, then saves it to a '-cleaned' version in the same directory + + :param str source_file_path: The absolute or relative path to the source .py file + :param str cleaned_suffix: The suffix to add to the cleaned file, typically left as default + :param tuple version: The (Major, Minor) version of python to parse with; must be at least (3, 4); defaults to + same version as this script + """ + + # add the cleaned_suffix to the output_path + input_path = pathlib.Path(source_file_path).resolve() + output_path = input_path.with_stem(f"{input_path.stem}{cleaned_suffix}") + + with open(input_path, "r") as source_file: + normalized_source = normalize_source(source_file.read(), version=version) + + with open(output_path, "w") as cleaned_file: + cleaned_file.write(normalized_source) + + return output_path diff --git a/model_training/dataset_generation/upload_raw_dataset.py b/model_training/dataset_generation/upload_raw_dataset.py new file mode 100644 index 0000000..e930d9e --- /dev/null +++ b/model_training/dataset_generation/upload_raw_dataset.py @@ -0,0 +1,60 @@ +from io import BytesIO +from typing import Dict, List, Literal + +from datasets import load_dataset +from huggingface_hub import HfApi + +from .DatasetDescription import DatasetDescription + +LOCAL_DATASET = Dict[Literal["train", "test", "valid"], List[str]] + + +def upload_single_dataset(data_files: LOCAL_DATASET, dataset_name: str, dataset_card: str): + local_datasets = load_dataset("csv", data_files=data_files) + local_datasets.push_to_hub(dataset_name, private=True) + + dataset_card_with_stats = dataset_card + f"\n\nDataset Statistics:\n\n```\n{local_datasets}\n```" + + api = HfApi() + api.upload_file( + path_or_fileobj=BytesIO(bytes(dataset_card_with_stats, "utf-8")), + path_in_repo="README.md", + repo_id=dataset_name, + repo_type="dataset", + ) + + +def upload_dataset_to_huggingface(dataset_description: DatasetDescription): + formatted_data_requests = "\n".join(f"{str(req.source_path.resolve())}: (train: {req.num_train}, test: {req.num_test}, valid: {req.num_valid})" for req in dataset_description.data_requests) + dataset_card = f""" +# {dataset_description.name} + +Created by the Syssec team @ UTD + +Dataset Composition: + +``` +{formatted_data_requests} +``` + +Python version: `{".".join(map(str, dataset_description.version))}` +""" + + splits: List[Literal["train", "test", "valid"]] = [ + "train", + "test", + "valid", + ] + + # collect data files + segmentation_data_files: LOCAL_DATASET = {} + statement_data_files: LOCAL_DATASET = {} + for split in splits: + segmentation_data_files[split] = [str(path.resolve()) for path in (dataset_description.csv_dir / split / "segmentation").glob("*.csv")] + statement_data_files[split] = [str(path.resolve()) for path in (dataset_description.csv_dir / split / "statement").glob("*.csv")] + + # upload datasets + segmentation_dataset_name = f"{dataset_description.huggingface_user}/segmentation-{dataset_description.name}" + upload_single_dataset(segmentation_data_files, segmentation_dataset_name, dataset_card) + statement_dataset_name = f"{dataset_description.huggingface_user}/statement-{dataset_description.name}" + upload_single_dataset(statement_data_files, statement_dataset_name, dataset_card) diff --git a/model_training/prepare_dataset.py b/model_training/prepare_dataset.py new file mode 100644 index 0000000..90fcf69 --- /dev/null +++ b/model_training/prepare_dataset.py @@ -0,0 +1,66 @@ +import json +import logging +import pathlib +from typing import Union +import click + +from dataset_generation.bytecode2csv import create_csv_dataset +from dataset_generation.create_code_dataset import create_code_dataset +from dataset_generation.DatasetDescription import DataRequest, DatasetDescription +from dataset_generation.upload_raw_dataset import upload_dataset_to_huggingface +from pylingual.utils.get_logger import get_logger + + +def get_dataset_description_from_arg_json(json_path: str, logger: Union[logging.Logger, None] = None) -> DatasetDescription: + json_file_path = pathlib.Path(json_path) + + if not json_file_path.exists(): + raise FileNotFoundError(f"{json_file_path} does not exist") + + if logger: + logger.info(f"Loading dataset description from {json_file_path}...") + + with json_file_path.open() as json_file: + dataset_description_dict = json.load(json_file) + + dataset_description_dict["data_requests"] = [DataRequest(**d) for d in dataset_description_dict["data_requests"]] + return DatasetDescription(**dataset_description_dict) + + +@click.command(help="Samples, splits, processes, and uploads a given dataset described by JSON.") +@click.argument("json_path", type=str) +def main(json_path: str): + logger = get_logger("prepare-dataset") + + dataset_description = get_dataset_description_from_arg_json(json_path, logger) + logger.debug(dataset_description) + + if dataset_description.code_dir.exists(): + raise FileExistsError(f"{dataset_description.code_dir} already exists! The dataset name is probably already taken.") + + logger.info("Creating code dataset...") + if not (dataset_description.data_requests and dataset_description.code_dir and dataset_description.version): + logger.error("Dataset description is missing required fields") + exit(1) + create_code_dataset( + dataset_description.data_requests, + dataset_description.code_dir, + dataset_description.version, + logger, + ) + + # create csv dataset + logger.info("Converting code dataset to csv...") + create_csv_dataset( + dataset_description.code_dir, + dataset_description.csv_dir, + dataset_description.data_requests, + logger, + ) + + logger.info(f"Uploading {dataset_description.name} to HuggingFace...") + upload_dataset_to_huggingface(dataset_description) + + +if __name__ == "__main__": + main() diff --git a/model_training/sample_jsons/py36-sample-data.json b/model_training/sample_jsons/py36-sample-data.json new file mode 100644 index 0000000..9486176 --- /dev/null +++ b/model_training/sample_jsons/py36-sample-data.json @@ -0,0 +1,24 @@ +{ + "name": "sample_dataset_name", + "version": [3, 6], + "save_to_dir": "./save_dir/", + "huggingface_user": "sample_user", + + "data_requests": + [ + { + "name": "dataset", + "source_path": "./dataset", + "num_train": 200, + "num_test": 200, + "num_valid": 200 + }, + { + "name": "dataset2", + "source_path": "./dataset2", + "num_train": 200, + "num_test": 200, + "num_valid": 200 + } + ] +} diff --git a/model_training/sample_jsons/py36-sample-segmentation.json b/model_training/sample_jsons/py36-sample-segmentation.json new file mode 100644 index 0000000..e2a81cc --- /dev/null +++ b/model_training/sample_jsons/py36-sample-segmentation.json @@ -0,0 +1,18 @@ +{ + "base_repo_name": "sample_user/sample_segmenter_name", + "dataset_repo_name": "sample_user/segmentation-sample_dataset_name", + "pretrained_mlm_repo_name": "", + "cache_dir": "./cache-dir/", + "max_token_length": 512, + "dataset_percentage": 100, + "mlm_training_parameters": { + "batch_size": 48, + "epochs": 2, + "learning_rate": 5e-5 + }, + "segmentation_training_parameters": { + "batch_size": 48, + "epochs": 2, + "learning_rate": 2e-5 + } +} diff --git a/model_training/sample_jsons/py36-sample-statement.json b/model_training/sample_jsons/py36-sample-statement.json new file mode 100644 index 0000000..ceb1d8c --- /dev/null +++ b/model_training/sample_jsons/py36-sample-statement.json @@ -0,0 +1,16 @@ +{ + "base_repo_name": "sample_user/sample_name", + "dataset_repo_name": "sample_user/statement-sample_dataset_name", + "tokenizer_repo_name": "sample_user/sample_name-tok", + "pretrained_seq2seq_repo_name": "Salesforce/codet5-base", + "cache_dir": "./cache-dir/", + "max_token_length": 256, + "dataset_percentage": 100, + "do_eval": true, + "fp16": true, + "statement_training_parameters": { + "batch_size": 24, + "epochs": 2, + "learning_rate": 2e-5 + } +} diff --git a/model_training/segmentation/SegmentationConfiguration.py b/model_training/segmentation/SegmentationConfiguration.py new file mode 100644 index 0000000..cad70c5 --- /dev/null +++ b/model_training/segmentation/SegmentationConfiguration.py @@ -0,0 +1,74 @@ +import json +import logging +import pathlib +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class TrainingParameters: + batch_size: int + epochs: int + learning_rate: float + + +@dataclass +class SegmentationConfiguration: + base_repo_name: str + dataset_repo_name: str + pretrained_mlm_repo_name: str + cache_dir: pathlib.Path + max_token_length: int + dataset_percentage: int + mlm_training_parameters: TrainingParameters + segmentation_training_parameters: TrainingParameters + + @property + def tokenizer_repo_name(self): + return self.base_repo_name + "-tokenizer" + + @property + def tokenizer_json_path(self): + return self.cache_dir / "tokenizers" / self.tokenizer_repo_name / "tokenizer.json" + + @property + def tokenized_dataset_repo_name(self): + return self.dataset_repo_name + "-tokenized" + + @property + def mlm_repo_name(self): + return self.base_repo_name + "-mlm" + + @property + def mlm_dir(self): + return self.cache_dir / "models" / self.mlm_repo_name + + @property + def segmenter_repo_name(self): + return self.base_repo_name + "-segmenter" + + @property + def segmenter_dir(self): + return self.cache_dir / "models" / self.segmenter_repo_name + + @property + def dataset_dir(self): + return self.cache_dir / "datasets" / self.dataset_repo_name + + def __post_init__(self): + self.cache_dir = pathlib.Path(self.cache_dir) + + +def parse_segmentation_config_json(json_file_path: pathlib.Path, logger: Optional[logging.Logger] = None) -> SegmentationConfiguration: + if not json_file_path.exists(): + raise FileNotFoundError(f"{json_file_path} does not exist") + + if logger: + logger.info(f"Loading model description from {json_file_path}...") + + with json_file_path.open() as json_file: + segmentation_config_dict = json.load(json_file) + + segmentation_config_dict["mlm_training_parameters"] = TrainingParameters(**segmentation_config_dict["mlm_training_parameters"]) + segmentation_config_dict["segmentation_training_parameters"] = TrainingParameters(**segmentation_config_dict["segmentation_training_parameters"]) + return SegmentationConfiguration(**segmentation_config_dict) diff --git a/model_training/segmentation/tokenize_seg.py b/model_training/segmentation/tokenize_seg.py new file mode 100644 index 0000000..aaba7e1 --- /dev/null +++ b/model_training/segmentation/tokenize_seg.py @@ -0,0 +1,152 @@ +import ast +import functools +import os +import pathlib +import click + +from datasets import load_dataset +from huggingface_hub import hf_hub_download +from SegmentationConfiguration import SegmentationConfiguration, parse_segmentation_config_json +from pylingual.segmentation.sliding_window import sliding_window +from transformers import PreTrainedTokenizerFast + +bytecode_separator = " " + + +def load_tokenizer(tokenizer_repo_name: str, cache_dir: pathlib.Path) -> PreTrainedTokenizerFast: + tokenizer_dir = cache_dir / "tokenizers" / tokenizer_repo_name + + tokenizer_file = hf_hub_download(repo_id=tokenizer_repo_name, filename="tokenizer.json", token=True, cache_dir=str(tokenizer_dir)) + tokenizer = PreTrainedTokenizerFast( + tokenizer_file=tokenizer_file, + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", + ) + + return tokenizer + + +# we need to make sure we align all the labels with the proper words. +def align_labels_with_tokens(labels, word_ids): + label_names = ["B", "I", "E"] + id2label = {str(i): label for i, label in enumerate(label_names)} + label2id = {v: k for k, v in id2label.items()} + + new_labels = [] + current_word = None + for word_id in word_ids: + if word_id != current_word: + # Start of a new word! + current_word = word_id + label = -100 if word_id is None else int(label2id[labels[word_id]]) + new_labels.append(label) + elif word_id is None: + # Special token + new_labels.append(-100) + else: + # Same word as previous token + label = int(label2id[labels[word_id]]) + new_labels.append(label) + return new_labels + + +# the process function used for tokenize the dataset +def tokenize_and_align_labels(tokenizer: PreTrainedTokenizerFast, max_length: int, examples): + MAX_WINDOW_LENGTH = 512 + STEP_SIZE = 128 + + # parse the strings into lists to better work with the bytecode and boundaries + parsed_bc = [(codeobj.split(" "), ast.literal_eval(bounds)) for codeobj, bounds in zip(examples["bytecode"], examples["boundary"])] + + codeobj_tokens = [] + + # count the tokens for each bytecode instruction in a codeobj + for codeobj, bounds in parsed_bc: + token_list = [] + + for bc, bounds in zip(codeobj, bounds): + token_list.append(((bc, bounds), len(tokenizer(bc)[0]))) + + codeobj_tokens.append(token_list) + + windows = [sliding_window(codeobj, MAX_WINDOW_LENGTH, STEP_SIZE) for codeobj in codeobj_tokens] + + # remake examples using our windows + examples["boundary"] = [] + examples["bytecode"] = [] + + # go through each window + for window in windows: + for item in window: + # where we will temporarily store our bytecode and bounds + bytecode = [] + bounds = [] + + for bc in item[0]: + bytecode.append(bc[0]) + bounds.append(bc[1]) + + # append it into examples + examples["bytecode"].append(bytecode_separator.join(bytecode)) + examples["boundary"].append(str(bounds)) + + tokenized_inputs = tokenizer( + examples["bytecode"], + truncation=True, + max_length=max_length, + ) + + all_labels = examples["boundary"] + new_labels = [] + for i, labels in enumerate(all_labels): + labels = labels.replace("'", "").strip("][").split(", ") + word_ids = tokenized_inputs.word_ids(i) + labels_len = len(labels) + max_word_id = word_ids[-2] + # for those data might cause error due to the incorrect tokenization, we fix the data exceed-length issue and + # leave them here as some noisy data. + if max_word_id >= labels_len: + new_labels.append([-100] * max_word_id) + else: + new_labels.append(align_labels_with_tokens(labels, word_ids)) + + tokenized_inputs["labels"] = new_labels + + return tokenized_inputs + + +def tokenize_segmentation_dataset(config: SegmentationConfiguration): + raw_dataset = load_dataset(config.dataset_repo_name, token=True, cache_dir=str(config.dataset_dir)) + + tokenizer = load_tokenizer(config.tokenizer_repo_name, config.cache_dir) + prepped_tokenize_and_align_labels = functools.partial(tokenize_and_align_labels, tokenizer, config.max_token_length) + + # tokenize input dataset + column_names = raw_dataset["train"].column_names + tokenized_datasets = raw_dataset.map( + prepped_tokenize_and_align_labels, + batched=True, + remove_columns=column_names, + num_proc=os.cpu_count(), + desc="Tokenizing datasets", + ) + + tokenized_datasets.push_to_hub( + config.tokenized_dataset_repo_name, + private=True, + ) + + +@click.command(help="Script to tokenize the segmentation dataset given a segmentation json.") +@click.argument("json_path", type=str) +def main(json_path: str): + json_file_path = pathlib.Path(json_path) + segmentation_config = parse_segmentation_config_json(json_file_path) + tokenize_segmentation_dataset(segmentation_config) + + +if __name__ == "__main__": + main() diff --git a/model_training/segmentation/train_mlm.py b/model_training/segmentation/train_mlm.py new file mode 100644 index 0000000..989416b --- /dev/null +++ b/model_training/segmentation/train_mlm.py @@ -0,0 +1,195 @@ +import logging +import os +import pathlib +import click + +from datasets import load_dataset +from huggingface_hub import hf_hub_download, repo_exists +from SegmentationConfiguration import SegmentationConfiguration, parse_segmentation_config_json +from transformers import AutoModelForMaskedLM, DataCollatorForLanguageModeling, PreTrainedTokenizerFast, RobertaConfig, RobertaForMaskedLM, Trainer, TrainingArguments + +from pylingual.segmentation.sliding_window import sliding_window + +bytecode_separator = " " + + +def load_tokenizer(tokenizer_repo_name: str, cache_dir: pathlib.Path) -> PreTrainedTokenizerFast: + tokenizer_dir = cache_dir / "tokenizers" / tokenizer_repo_name + + tokenizer_file = hf_hub_download( + repo_id=tokenizer_repo_name, + filename="tokenizer.json", + token=True, + cache_dir=str(tokenizer_dir), + ) + tokenizer = PreTrainedTokenizerFast( + tokenizer_file=tokenizer_file, + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", + ) + + return tokenizer + + +def load_tokenized_train_dataset( + dataset_repo_name: str, + tokenizer: PreTrainedTokenizerFast, + max_length: int, + cache_dir: pathlib.Path, +): + dataset_dir = cache_dir / "datasets" / dataset_repo_name + raw_dataset = load_dataset(dataset_repo_name, token=True, cache_dir=dataset_dir, split="train") + + # tokenize the input data + column_names = raw_dataset.column_names + + def tokenize(examples): + # sliding window compatibility + MAX_WINDOW_LENGTH = 512 + STEP_SIZE = 128 + + # parse the strings into lists to better work with the bytecode and boundaries + parsed_bc = [codeobj.split(" ") for codeobj in examples["bytecode"]] + + codeobj_tokens = [] + + # count the tokens for each bytecode instruction in a codeobj + for codeobj in parsed_bc: + token_list = [] + + for bytecode in codeobj: + token_list.append((bytecode, len(tokenizer(bytecode)[0]))) + + codeobj_tokens.append(token_list) + + windows = [sliding_window(codeobj, MAX_WINDOW_LENGTH, STEP_SIZE) for codeobj in codeobj_tokens] + + # remake examples using our windows + examples["bytecode"] = [] + + # go through each window + for window in windows: + for item in window: + # where we will temporarily store our bytecode and bounds + bytecode = [] + + for bc in item[0]: + bytecode.append(bc) + + # append to examples + examples["bytecode"].append(bytecode_separator.join(bytecode)) + + return tokenizer(examples["bytecode"], max_length=max_length, truncation=True) + + tokenized_dataset = raw_dataset.map( + tokenize, + batched=True, + remove_columns=column_names, + num_proc=os.cpu_count(), + desc="Tokenizing datasets", + ) + + return tokenized_dataset + + +def load_pretrained_mlm( + pretrained_mlm_repo_name: str, + tokenizer_embedding_length: int, + cache_dir: pathlib.Path, +) -> AutoModelForMaskedLM: + # load a basic pretrained BERT model + pretrained_mlm_dir = cache_dir / "models" / pretrained_mlm_repo_name + model = AutoModelForMaskedLM.from_pretrained(pretrained_mlm_repo_name, cache_dir=str(pretrained_mlm_dir)) + + # resize token embeddings to fit the model + model.resize_token_embeddings(tokenizer_embedding_length) + + return model + + +def initialize_untrained_mlm( + tokenizer_embedding_length: int, + max_token_length: int, +) -> RobertaForMaskedLM: + # initialize untrained RoBERTa model + # most configuration options set to match https://huggingface.co/microsoft/codebert-base/blob/main/config.json for direct comparison + model_config = RobertaConfig( + max_position_embeddings=max_token_length, # INPUT LENGTH LIMIT + vocab_size=tokenizer_embedding_length, + layer_norm_eps=1e-05, + type_vocab_size=1, + ) + model = RobertaForMaskedLM(model_config) + + return model + + +def train_mlm(config: SegmentationConfiguration): + if repo_exists(config.base_repo_name): + logging.error(f"{config.base_repo_name} has already exists") + exit(1) + + using_pretrained_model = bool(config.pretrained_mlm_repo_name) + # train model, for now the configuration comes from a regular T5 translation model. + training_args = TrainingArguments( + output_dir=str(config.mlm_dir), + num_train_epochs=config.mlm_training_parameters.epochs, + per_device_train_batch_size=config.mlm_training_parameters.batch_size, + save_steps=1000, + save_total_limit=5, + prediction_loss_only=True, + push_to_hub=True, + hub_model_id=config.mlm_repo_name, + hub_private_repo=True, + ddp_backend="nccl", + ddp_find_unused_parameters=using_pretrained_model, # only look for unused parameters in pretrained models + remove_unused_columns=False, + ) + + tokenizer = load_tokenizer(config.tokenizer_repo_name, config.cache_dir) + + # Set DataCollator for MLM task, set the probability of masking. + data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=True, mlm_probability=0.15) + + if using_pretrained_model: + pretrained_mlm = load_pretrained_mlm(config.pretrained_mlm_repo_name, len(tokenizer), config.cache_dir) + else: + pretrained_mlm = initialize_untrained_mlm(len(tokenizer), config.max_token_length + 2) + + tokenized_training_data = load_tokenized_train_dataset(config.dataset_repo_name, tokenizer, config.max_token_length, config.cache_dir) + + # Hugging face trainer: a Trainer class to fine-tune pretrained models + trainer = Trainer( + model=pretrained_mlm, + args=training_args, + data_collator=data_collator, + train_dataset=tokenized_training_data, + ) + + # Training + trainer.train() + + if int(os.environ["LOCAL_RANK"]) == 0: + # Save the model + trainer.save_model(config.mlm_dir) + + trainer.push_to_hub( + finetuned_from=config.pretrained_mlm_repo_name, + dataset=config.dataset_repo_name, + commit_message=f"Trained on {config.dataset_repo_name} using {config.tokenizer_repo_name}", + ) + + +@click.command(help="Training script for the masked language model pretraining for the segmentation model given a segmentation json.") +@click.argument("json_path", type=str) +def main(json_path: str): + json_file_path = pathlib.Path(json_path) + segmentation_config = parse_segmentation_config_json(json_file_path) + train_mlm(segmentation_config) + + +if __name__ == "__main__": + main() diff --git a/model_training/segmentation/train_seg.py b/model_training/segmentation/train_seg.py new file mode 100644 index 0000000..941b39d --- /dev/null +++ b/model_training/segmentation/train_seg.py @@ -0,0 +1,155 @@ +import logging +import os +import pathlib +import click + +import evaluate +import numpy as np +from datasets import ReadInstruction, load_dataset +from huggingface_hub import hf_hub_download, repo_exists +from SegmentationConfiguration import SegmentationConfiguration, parse_segmentation_config_json +from transformers import AutoModelForTokenClassification, DataCollatorForTokenClassification, PreTrainedTokenizerFast, Trainer, TrainingArguments + +# two dictionaries, id2label and label2id, which contain the mappings from ID to label and vice versa. +label_names = ["B", "I", "E"] +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} + + +# compute_metrics: evaluate metric for training and evaluation. +def compute_metrics(eval_preds): + metric = evaluate.load("seqeval") + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Remove ignored index (special tokens) and convert to labels + # noqa: E741 + true_labels = [[label_names[l] for l in label if l != -100] for label in labels] + true_predictions = [[label_names[p] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels)] + all_metrics = metric.compute(predictions=true_predictions, references=true_labels) + return { + "precision": all_metrics["overall_precision"], + "recall": all_metrics["overall_recall"], + "f1": all_metrics["overall_f1"], + "accuracy": all_metrics["overall_accuracy"], + } + + +def load_tokenizer(tokenizer_repo_name: str, cache_dir: pathlib.Path) -> PreTrainedTokenizerFast: + tokenizer_dir = cache_dir / "tokenizers" / tokenizer_repo_name + + tokenizer_file = hf_hub_download( + repo_id=tokenizer_repo_name, + filename="tokenizer.json", + token=True, + cache_dir=str(tokenizer_dir), + ) + tokenizer = PreTrainedTokenizerFast( + tokenizer_file=tokenizer_file, + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", + ) + + return tokenizer + + +def load_tokenized_train_and_valid_dataset(dataset_repo_name: str, cache_dir: pathlib.Path, dataset_percentage: int = 100): + dataset_dir = cache_dir / "datasets" / dataset_repo_name + # Load the tokenized dataset + tokenized_train_dataset = load_dataset( + dataset_repo_name, + token=True, + cache_dir=str(dataset_dir), + split=ReadInstruction("train", to=dataset_percentage, unit="%"), + ) + + tokenized_validation_dataset = load_dataset( + dataset_repo_name, + token=True, + cache_dir=str(dataset_dir), + split="valid", + ) + + return tokenized_train_dataset, tokenized_validation_dataset + + +def train_segmentation_model(config: SegmentationConfiguration): + if repo_exists(config.base_repo_name): + logging.error(f"{config.base_repo_name} has already exists") + exit(1) + # training arguments. + training_args = TrainingArguments( + output_dir=str(config.segmenter_dir), + overwrite_output_dir=True, + eval_strategy="epoch", + logging_strategy="epoch", + save_strategy="epoch", + learning_rate=config.segmentation_training_parameters.learning_rate, + num_train_epochs=config.segmentation_training_parameters.epochs, + per_device_train_batch_size=config.segmentation_training_parameters.batch_size, + save_steps=1000, + weight_decay=0.01, + fp16=True, + push_to_hub=True, + hub_model_id=config.segmenter_repo_name, + hub_private_repo=True, + ddp_backend="nccl", + ddp_find_unused_parameters=True, + save_total_limit=5, + ) + + # load a basic pretrained BERT model + model = AutoModelForTokenClassification.from_pretrained( + pretrained_model_name_or_path=config.mlm_repo_name, + id2label=id2label, + label2id=label2id, + token=True, + ) + + # Set DataCollator for DataCollatorForTokenClassification + tokenizer = load_tokenizer(config.tokenizer_repo_name, config.cache_dir) + data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer, max_length=config.max_token_length) + + ( + tokenized_train_dataset, + tokenized_validation_dataset, + ) = load_tokenized_train_and_valid_dataset(config.tokenized_dataset_repo_name, config.cache_dir, config.dataset_percentage) + + # Hugging face trainer: a Trainer class to fine-tune pretrained models + trainer = Trainer( + model=model, + args=training_args, + data_collator=data_collator, + train_dataset=tokenized_train_dataset, + eval_dataset=tokenized_validation_dataset, + compute_metrics=compute_metrics, + tokenizer=tokenizer, + ) + + # Training + trainer.train() + + if int(os.environ["LOCAL_RANK"]) == 0: + # Save the model + trainer.save_model(str(config.segmenter_dir)) + + trainer.push_to_hub( + finetuned_from=config.mlm_repo_name, + dataset=config.tokenized_dataset_repo_name, + commit_message=f"Trained on {config.tokenized_dataset_repo_name} using {config.mlm_repo_name}", + ) + + +@click.command(help="Training script for the segmentation model given a segmentation json.") +@click.argument("json_path", type=str) +def main(json_path: str): + json_file_path = pathlib.Path(json_path) + segmentation_config = parse_segmentation_config_json(json_file_path) + train_segmentation_model(segmentation_config) + + +if __name__ == "__main__": + main() diff --git a/model_training/segmentation/train_tokenizer.py b/model_training/segmentation/train_tokenizer.py new file mode 100644 index 0000000..ba74c80 --- /dev/null +++ b/model_training/segmentation/train_tokenizer.py @@ -0,0 +1,96 @@ +import logging +import pathlib +import click + +from datasets import ReadInstruction, load_dataset +from huggingface_hub import HfApi, create_repo, repo_exists +from SegmentationConfiguration import SegmentationConfiguration, parse_segmentation_config_json +from tokenizers import Tokenizer, decoders, models, normalizers, pre_tokenizers, processors, trainers + +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] + + +def get_untrained_tokenizer() -> Tokenizer: + # WordPiece tokenization for BERT. + tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) + + # The normalizer recognizes the accented characters and strip them out. + tokenizer.normalizer = normalizers.Sequence([normalizers.NFD(), normalizers.StripAccents()]) + + # The pre-tokenizer splits on tokens. + tokenizer.pre_tokenizer = pre_tokenizers.Split("", "removed") + + return tokenizer + + +def post_training_configuration(tokenizer: Tokenizer): + cls_token_id = tokenizer.token_to_id("[CLS]") + sep_token_id = tokenizer.token_to_id("[SEP]") + + # Set decoder for the tokenizer + tokenizer.decoder = decoders.WordPiece(prefix="##") + + # For the TemplateProcessor, we have to specify how to treat a single sentence and a pair of sentences. + tokenizer.post_processor = processors.TemplateProcessing( + single="[CLS]:0 $A:0 [SEP]:0", + pair="[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1", + special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)], + ) + + +def save_and_upload_tokenizer( + tokenizer: Tokenizer, + tokenizer_json_path: pathlib.Path, + tokenizer_repo_name: str, + dataset_name: str, +): + # save the tokenizer locally + tokenizer_json_path.parent.mkdir(parents=True, exist_ok=True) + tokenizer.save(str(tokenizer_json_path.resolve())) + + # upload tokenizer to huggingface + api = HfApi() + create_repo(tokenizer_repo_name, exist_ok=True, private=True) + api.upload_file( + path_in_repo="tokenizer.json", + path_or_fileobj=str(tokenizer_json_path.resolve()), + repo_id=tokenizer_repo_name, + commit_message=f"Trained tokenizer using {dataset_name}", + ) + + +def train_tokenizer(config: SegmentationConfiguration): + if repo_exists(config.base_repo_name): + logging.error(f"{config.base_repo_name} has already exists") + exit(1) + + tokenizer = get_untrained_tokenizer() + + train_dataset = load_dataset( + config.dataset_repo_name, + token=True, + split=ReadInstruction("train", to=config.dataset_percentage, unit="%"), + )["bytecode"] + trainer = trainers.WordPieceTrainer(vocab_size=30000, special_tokens=special_tokens) + tokenizer.train_from_iterator(train_dataset, trainer=trainer) + + post_training_configuration(tokenizer) + + save_and_upload_tokenizer( + tokenizer, + config.tokenizer_json_path, + config.tokenizer_repo_name, + config.dataset_repo_name, + ) + + +@click.command(help="Training script for the bytecode tokenizer for the segmentation model given a segmentation json.") +@click.argument("json_path", type=str) +def main(json_path: str): + json_file_path = pathlib.Path(json_path) + segmentation_config = parse_segmentation_config_json(json_file_path) + train_tokenizer(segmentation_config) + + +if __name__ == "__main__": + main() diff --git a/model_training/statement/README.md b/model_training/statement/README.md new file mode 100644 index 0000000..e0c58a7 --- /dev/null +++ b/model_training/statement/README.md @@ -0,0 +1,18 @@ +# seq2seq + +- train_tokenizer_auto.py: + - trains the manual tokenizer + +- tokenize_seq2seq.py: + - tokenize the dataset for the seq2seq model + +- train_seq2seq.py: + - finetuning the pretrained model + - will create a sequence-to-sequence translation model + +- StatementConfiguration.py + - defines the JSON format for statement translation training + +# manual1 + +Contains JSONs mapping bytecode instructions and their configurations to use in training. diff --git a/model_training/statement/StatementConfiguration.py b/model_training/statement/StatementConfiguration.py new file mode 100644 index 0000000..a6c2b37 --- /dev/null +++ b/model_training/statement/StatementConfiguration.py @@ -0,0 +1,59 @@ +from dataclasses import dataclass + +import pathlib +import json +import logging + + +@dataclass +class TrainingParameters: + batch_size: int + epochs: int + learning_rate: float + + +@dataclass +class StatementConfiguration: + base_repo_name: str + dataset_repo_name: str + tokenizer_repo_name: str + pretrained_seq2seq_repo_name: str + cache_dir: pathlib.Path + max_token_length: int + dataset_percentage: int + do_eval: bool + fp16: bool + statement_training_parameters: TrainingParameters + + @property + def tokenized_dataset_repo_name(self): + return self.dataset_repo_name + "-tokenized" + + @property + def statement_model_repo_name(self): + return self.base_repo_name + "-statement" + + @property + def statement_model_dir(self): + return self.cache_dir / "models" / self.statement_model_repo_name + + @property + def log_dir(self): + return self.statement_model_dir / "logs" + + def __post_init__(self): + self.cache_dir = pathlib.Path(self.cache_dir) + + +def parse_statement_config_json(json_file_path: pathlib.Path, logger: logging.Logger = None) -> StatementConfiguration: + if not json_file_path.exists(): + raise FileNotFoundError(f"{json_file_path} does not exist") + + if logger: + logger.info(f"Loading model description from {json_file_path}...") + + with json_file_path.open() as json_file: + statement_config_dict = json.load(json_file) + + statement_config_dict["statement_training_parameters"] = TrainingParameters(**statement_config_dict["statement_training_parameters"]) + return StatementConfiguration(**statement_config_dict) diff --git a/model_training/statement/__init__.py b/model_training/statement/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model_training/statement/tokenize_seq2seq.py b/model_training/statement/tokenize_seq2seq.py new file mode 100644 index 0000000..1a17dcd --- /dev/null +++ b/model_training/statement/tokenize_seq2seq.py @@ -0,0 +1,51 @@ +import os +from typing import Any + +import click +import pathlib + +from datasets import load_dataset +from transformers import RobertaTokenizer + +from StatementConfiguration import StatementConfiguration, parse_statement_config_json + +import functools + + +def preprocess_function(tokenizer: RobertaTokenizer, max_token_length: int, input_key: str, examples: dict[str, Any]) -> dict[str, Any]: + """Set up Huggingface tokenizers for both inputs and targets""" + inputs = [ex if ex else "" for ex in examples[input_key]] + targets = [ex if ex else "" for ex in examples["source"]] + + return tokenizer(text=inputs, text_target=targets, max_length=max_token_length, truncation=True) + + +def tokenize_seq2seq_dataset(config: StatementConfiguration): + # ref: https://huggingface.co/Salesforce/codet5-base + tokenizer = RobertaTokenizer.from_pretrained(config.tokenizer_repo_name) + raw_datasets = load_dataset(config.dataset_repo_name, token=True) + + column_names = raw_datasets["train"].column_names + input_key = "bytecode" + prepped_preprocess_function = functools.partial(preprocess_function, tokenizer, config.max_token_length, input_key) + tokenized_datasets = raw_datasets.map( + prepped_preprocess_function, + batched=True, + remove_columns=column_names, + num_proc=os.cpu_count(), + desc="Tokenizing datasets", + ) + + tokenized_datasets.push_to_hub(config.tokenized_dataset_repo_name, private=True) + + +@click.command(help="Tokenization script for Statement Translation model given a statement json.") +@click.argument("json_path", type=str) +def main(json_path: str): + json_file_path = pathlib.Path(json_path) + statement_config = parse_statement_config_json(json_file_path) + tokenize_seq2seq_dataset(statement_config) + + +if __name__ == "__main__": + main() diff --git a/model_training/statement/tokenizer/special_tokens_map.json b/model_training/statement/tokenizer/special_tokens_map.json new file mode 100644 index 0000000..5d7a5d0 --- /dev/null +++ b/model_training/statement/tokenizer/special_tokens_map.json @@ -0,0 +1,5471 @@ +{ + "additional_special_tokens": [ + { + "content": "-=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "<<", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": ">>", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": ":=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": ">=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "<=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "==", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "!=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "+=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "//=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "**=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "/=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "//", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "%=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "@=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "&=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "|=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "^=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": ">>=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "<<=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "*=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "()", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "):", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "~>>", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "**", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "E->", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "defaults", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "args:", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "vararg:", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "E-END", + "lstrip": true, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "~~>", + "lstrip": true, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": true, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": true, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "False", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "None", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "True", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "and", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "assert", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "async", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "await", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "break", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "class", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "continue", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "def", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "del", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "elif", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "else", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "else:", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "except", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "except:", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "finally", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "finally:", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "for", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "from", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "global", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "if", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "import", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "in", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "is", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "lambda", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "nonlocal", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "not", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "or", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "pass", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "raise", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "return", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "try", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "try:", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "while", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "with", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "yield", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "case", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "as", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "ASYNC_GEN_WRAP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BEFORE_ASYNC_WITH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BEFORE_WITH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BEGIN_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_ADD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_AND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_FLOOR_DIVIDE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_LSHIFT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_MATRIX_MULTIPLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_MODULO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_MULTIPLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_OP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_OR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_POWER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_RSHIFT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_SLICE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_SUBSCR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_SUBTRACT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_TRUE_DIVIDE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BINARY_XOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BREAK_LOOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_CONST_KEY_MAP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_LIST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_LIST_UNPACK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_MAP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_MAP_UNPACK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_MAP_UNPACK_WITH_CALL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_SET", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_SET_UNPACK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_SLICE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_STRING", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_TUPLE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_TUPLE_UNPACK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "BUILD_TUPLE_UNPACK_WITH_CALL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CACHE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CALL_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CALL_FUNCTION", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CALL_FUNCTION_EX", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CALL_FUNCTION_KW", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CALL_METHOD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CHECK_EG_MATCH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CHECK_EXC_MATCH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CLEANUP_THROW", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "COMPARE_OP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CONTAINS_OP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CONTINUE_LOOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "COPY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "COPY_DICT_WITHOUT_KEYS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "COPY_FREE_VARS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DELETE_ATTR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DELETE_DEREF", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DELETE_FAST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DELETE_GLOBAL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DELETE_NAME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DELETE_SUBSCR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DICT_MERGE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DICT_UPDATE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DUP_TOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "DUP_TOP_TWO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "END_ASYNC_FOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "END_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "END_FOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "END_SEND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "EXTENDED_ARG", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "FOR_ITER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "FORMAT_VALUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "FORMAT_SIMPLE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "FORMAT_SPEC", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "GEN_START", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "GET_AITER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "GET_ANEXT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "GET_AWAITABLE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "GET_ITER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "GET_LEN", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "GET_YIELD_FROM_ITER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "IMPORT_FROM", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "IMPORT_NAME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "IMPORT_STAR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_ADD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_AND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_FLOOR_DIVIDE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_LSHIFT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_MATRIX_MULTIPLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_MODULO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_MULTIPLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_OR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_POWER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_RSHIFT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_SUBTRACT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_TRUE_DIVIDE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INPLACE_XOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "INTERPRETER_EXIT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "IS_OP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "JUMP_ABSOLUTE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "JUMP_BACKWARD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "JUMP_BACKWARD_NO_INTERRUPT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "JUMP_FORWARD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "JUMP_IF_FALSE_OR_POP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "JUMP_IF_NOT_EXC_MATCH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "JUMP_IF_TRUE_OR_POP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LIST_APPEND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LIST_EXTEND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LIST_TO_TUPLE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_ASSERTION_ERROR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_ATTR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_BUILD_CLASS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_CLASSDEREF", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_CLOSURE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_CONST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_FAST_LOAD_FAST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_FROM_DICT_OR_DEREF", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_FROM_DICT_OR_GLOBAL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_FAST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_FAST_AND_CLEAR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_FAST_CHECK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_GLOBAL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_LOCALS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_METHOD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_NAME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "LOAD_SUPER_ATTR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "MAKE_CELL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "MAKE_FUNCTION", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SET_FUNCTION_ATTRIBUTE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "MAP_ADD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "MATCH_CLASS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "MATCH_KEYS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "MATCH_MAPPING", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "MATCH_SEQUENCE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "NOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_BLOCK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_EXCEPT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_JUMP_FORWARD_IF_FALSE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_JUMP_FORWARD_IF_NONE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_JUMP_FORWARD_IF_NOT_NONE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_JUMP_FORWARD_IF_TRUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_JUMP_IF_FALSE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_JUMP_IF_NONE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_JUMP_IF_NOT_NONE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_JUMP_IF_TRUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "POP_TOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "PRECALL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "PREP_RERAISE_STAR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "PRINT_EXPR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "PUSH_EXC_INFO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "PUSH_NULL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "RAISE_VARARGS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "RERAISE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "RESERVED", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "RESUME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "RETURN_CONST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "RETURN_GENERATOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "RETURN_VALUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "CONVERT_VALUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "ROT_FOUR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "ROT_N", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "ROT_THREE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "ROT_TWO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "ENTER_EXECUTOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "TO_BOOL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SEND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SET_ADD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SET_UPDATE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SETUP_ANNOTATIONS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SETUP_ASYNC_WITH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SETUP_EXCEPT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SETUP_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SETUP_LOOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SETUP_WITH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "STORE_ATTR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "STORE_DEREF", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "STORE_FAST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "STORE_FAST_LOAD_FAST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "STORE_FAST_STORE_FAST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "STORE_GLOBAL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "STORE_NAME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "STORE_SLICE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "STORE_SUBSCR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "SWAP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "UNARY_INVERT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "UNARY_NEGATIVE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "UNARY_NOT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "UNARY_POSITIVE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "UNPACK_EX", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "UNPACK_SEQUENCE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "WITH_CLEANUP_FINISH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "WITH_CLEANUP_START", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "WITH_EXCEPT_START", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "YIELD_FROM", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "YIELD_VALUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "match", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "type", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "HAVE_ARGUMENT", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "CALL_KW", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "CALL_INTRINSIC_1", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "CALL_INTRINSIC_2", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "JUMP_NO_INTERRUPT", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "nargs", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "vargs", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "compare", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "name", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "const", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + }, + { + "content": "local", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false + } + ], + "bos_token": { + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "cls_token": { + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "eos_token": { + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "mask_token": { + "content": "", + "lstrip": true, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "pad_token": { + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "sep_token": { + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "unk_token": { + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + } +} diff --git a/model_training/statement/tokenizer/tokenizer.json b/model_training/statement/tokenizer/tokenizer.json new file mode 100644 index 0000000..32cba79 --- /dev/null +++ b/model_training/statement/tokenizer/tokenizer.json @@ -0,0 +1,7775 @@ +{ + "version": "1.0", + "truncation": null, + "padding": null, + "added_tokens": [ + { + "id": 100, + "content": "-=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 101, + "content": "<<", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 102, + "content": ">>", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 103, + "content": ":=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 104, + "content": ">=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 105, + "content": "<=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 106, + "content": "==", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 107, + "content": "!=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 108, + "content": "+=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 109, + "content": "//=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 110, + "content": "**=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 111, + "content": "/=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 112, + "content": "//", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 113, + "content": "%=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 114, + "content": "@=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 115, + "content": "&=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 116, + "content": "|=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 117, + "content": "^=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 118, + "content": ">>=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 119, + "content": "<<=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 120, + "content": "*=", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 121, + "content": "()", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 122, + "content": "):", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 123, + "content": "~>>", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 124, + "content": "**", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 125, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 127, + "content": "E->", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 128, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 129, + "content": "defaults", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 130, + "content": "args:", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 131, + "content": "vararg:", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 132, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 133, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 134, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 135, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 136, + "content": "E-END", + "lstrip": true, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 137, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 138, + "content": "~~>", + "lstrip": true, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 139, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 140, + "content": "", + "lstrip": true, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 141, + "content": "", + "lstrip": true, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 142, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 143, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 144, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 145, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 146, + "content": "", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 147, + "content": "False", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 148, + "content": "None", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 149, + "content": "True", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 150, + "content": "and", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 151, + "content": "assert", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 152, + "content": "async", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 153, + "content": "await", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 154, + "content": "break", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 155, + "content": "class", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 156, + "content": "continue", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 157, + "content": "def", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 158, + "content": "del", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 159, + "content": "elif", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 160, + "content": "else", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 161, + "content": "else:", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 162, + "content": "except", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 163, + "content": "except:", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 164, + "content": "finally", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 165, + "content": "finally:", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 166, + "content": "for", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 167, + "content": "from", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 168, + "content": "global", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 169, + "content": "if", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 170, + "content": "import", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 171, + "content": "in", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 172, + "content": "is", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 173, + "content": "lambda", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 174, + "content": "nonlocal", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 175, + "content": "not", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 176, + "content": "or", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 177, + "content": "pass", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 178, + "content": "raise", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 179, + "content": "return", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 180, + "content": "try", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 181, + "content": "try:", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 182, + "content": "while", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 183, + "content": "with", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 184, + "content": "yield", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 185, + "content": "case", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 186, + "content": "as", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 187, + "content": "ASYNC_GEN_WRAP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 188, + "content": "BEFORE_ASYNC_WITH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 189, + "content": "BEFORE_WITH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 190, + "content": "BEGIN_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 191, + "content": "BINARY_ADD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 192, + "content": "BINARY_AND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 193, + "content": "BINARY_FLOOR_DIVIDE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 194, + "content": "BINARY_LSHIFT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 195, + "content": "BINARY_MATRIX_MULTIPLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 196, + "content": "BINARY_MODULO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 197, + "content": "BINARY_MULTIPLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 198, + "content": "BINARY_OP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 199, + "content": "BINARY_OR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 200, + "content": "BINARY_POWER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 201, + "content": "BINARY_RSHIFT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 202, + "content": "BINARY_SLICE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 203, + "content": "BINARY_SUBSCR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 204, + "content": "BINARY_SUBTRACT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 205, + "content": "BINARY_TRUE_DIVIDE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 206, + "content": "BINARY_XOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 207, + "content": "BREAK_LOOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 208, + "content": "BUILD_CONST_KEY_MAP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 209, + "content": "BUILD_LIST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 210, + "content": "BUILD_LIST_UNPACK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 211, + "content": "BUILD_MAP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 212, + "content": "BUILD_MAP_UNPACK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 213, + "content": "BUILD_MAP_UNPACK_WITH_CALL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 214, + "content": "BUILD_SET", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 215, + "content": "BUILD_SET_UNPACK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 216, + "content": "BUILD_SLICE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 217, + "content": "BUILD_STRING", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 218, + "content": "BUILD_TUPLE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 219, + "content": "BUILD_TUPLE_UNPACK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 220, + "content": "BUILD_TUPLE_UNPACK_WITH_CALL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 221, + "content": "CACHE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 222, + "content": "CALL_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 223, + "content": "CALL_FUNCTION", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 224, + "content": "CALL_FUNCTION_EX", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 225, + "content": "CALL_FUNCTION_KW", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 226, + "content": "CALL_METHOD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 227, + "content": "CHECK_EG_MATCH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 228, + "content": "CHECK_EXC_MATCH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 229, + "content": "CLEANUP_THROW", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 230, + "content": "COMPARE_OP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 231, + "content": "CONTAINS_OP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 232, + "content": "CONTINUE_LOOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 233, + "content": "COPY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 234, + "content": "COPY_DICT_WITHOUT_KEYS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 235, + "content": "COPY_FREE_VARS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 236, + "content": "DELETE_ATTR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 237, + "content": "DELETE_DEREF", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 238, + "content": "DELETE_FAST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 239, + "content": "DELETE_GLOBAL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 240, + "content": "DELETE_NAME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 241, + "content": "DELETE_SUBSCR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 242, + "content": "DICT_MERGE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 243, + "content": "DICT_UPDATE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 244, + "content": "DUP_TOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 245, + "content": "DUP_TOP_TWO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 246, + "content": "END_ASYNC_FOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 247, + "content": "END_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 248, + "content": "END_FOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 249, + "content": "END_SEND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 250, + "content": "EXTENDED_ARG", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 251, + "content": "FOR_ITER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 252, + "content": "FORMAT_VALUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 253, + "content": "GEN_START", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 254, + "content": "GET_AITER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 255, + "content": "GET_ANEXT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 256, + "content": "GET_AWAITABLE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 257, + "content": "GET_ITER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 258, + "content": "GET_LEN", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 259, + "content": "GET_YIELD_FROM_ITER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 260, + "content": "IMPORT_FROM", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 261, + "content": "IMPORT_NAME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 262, + "content": "IMPORT_STAR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 263, + "content": "INPLACE_ADD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 264, + "content": "INPLACE_AND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 265, + "content": "INPLACE_FLOOR_DIVIDE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 266, + "content": "INPLACE_LSHIFT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 267, + "content": "INPLACE_MATRIX_MULTIPLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 268, + "content": "INPLACE_MODULO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 269, + "content": "INPLACE_MULTIPLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 270, + "content": "INPLACE_OR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 271, + "content": "INPLACE_POWER", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 272, + "content": "INPLACE_RSHIFT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 273, + "content": "INPLACE_SUBTRACT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 274, + "content": "INPLACE_TRUE_DIVIDE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 275, + "content": "INPLACE_XOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 276, + "content": "INTERPRETER_EXIT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 277, + "content": "IS_OP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 278, + "content": "JUMP_ABSOLUTE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 279, + "content": "JUMP_BACKWARD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 280, + "content": "JUMP_BACKWARD_NO_INTERRUPT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 281, + "content": "JUMP_FORWARD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 282, + "content": "JUMP_IF_FALSE_OR_POP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 283, + "content": "JUMP_IF_NOT_EXC_MATCH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 284, + "content": "JUMP_IF_TRUE_OR_POP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 285, + "content": "LIST_APPEND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 286, + "content": "LIST_EXTEND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 287, + "content": "LIST_TO_TUPLE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 288, + "content": "LOAD_ASSERTION_ERROR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 289, + "content": "LOAD_ATTR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 290, + "content": "LOAD_BUILD_CLASS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 291, + "content": "LOAD_CLASSDEREF", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 292, + "content": "LOAD_CLOSURE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 293, + "content": "LOAD_CONST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 294, + "content": "LOAD_DEREF", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 295, + "content": "LOAD_FAST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 296, + "content": "LOAD_FAST_AND_CLEAR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 297, + "content": "LOAD_FAST_CHECK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 298, + "content": "LOAD_GLOBAL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 299, + "content": "LOAD_LOCALS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 300, + "content": "LOAD_METHOD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 301, + "content": "LOAD_NAME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 302, + "content": "LOAD_SUPER_ATTR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 303, + "content": "MAKE_CELL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 304, + "content": "MAKE_FUNCTION", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 305, + "content": "MAP_ADD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 306, + "content": "MATCH_CLASS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 307, + "content": "MATCH_KEYS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 308, + "content": "MATCH_MAPPING", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 309, + "content": "MATCH_SEQUENCE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 310, + "content": "NOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 311, + "content": "POP_BLOCK", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 312, + "content": "POP_EXCEPT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 313, + "content": "POP_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 314, + "content": "POP_JUMP_FORWARD_IF_FALSE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 315, + "content": "POP_JUMP_FORWARD_IF_NONE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 316, + "content": "POP_JUMP_FORWARD_IF_NOT_NONE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 317, + "content": "POP_JUMP_FORWARD_IF_TRUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 318, + "content": "POP_JUMP_IF_FALSE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 319, + "content": "POP_JUMP_IF_NONE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 320, + "content": "POP_JUMP_IF_NOT_NONE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 321, + "content": "POP_JUMP_IF_TRUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 322, + "content": "POP_TOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 323, + "content": "PRECALL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 324, + "content": "PREP_RERAISE_STAR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 325, + "content": "PRINT_EXPR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 326, + "content": "PUSH_EXC_INFO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 327, + "content": "PUSH_NULL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 328, + "content": "RAISE_VARARGS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 329, + "content": "RERAISE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 330, + "content": "RESERVED", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 331, + "content": "RESUME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 332, + "content": "RETURN_CONST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 333, + "content": "RETURN_GENERATOR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 334, + "content": "RETURN_VALUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 335, + "content": "ROT_FOUR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 336, + "content": "ROT_N", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 337, + "content": "ROT_THREE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 338, + "content": "ROT_TWO", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 339, + "content": "SEND", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 340, + "content": "SET_ADD", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 341, + "content": "SET_UPDATE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 342, + "content": "SETUP_ANNOTATIONS", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 343, + "content": "SETUP_ASYNC_WITH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 344, + "content": "SETUP_EXCEPT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 345, + "content": "SETUP_FINALLY", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 346, + "content": "SETUP_LOOP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 347, + "content": "SETUP_WITH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 348, + "content": "STORE_ATTR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 349, + "content": "STORE_DEREF", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 350, + "content": "STORE_FAST", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 351, + "content": "STORE_GLOBAL", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 352, + "content": "STORE_NAME", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 353, + "content": "STORE_SLICE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 354, + "content": "STORE_SUBSCR", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 355, + "content": "SWAP", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 356, + "content": "UNARY_INVERT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 357, + "content": "UNARY_NEGATIVE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 358, + "content": "UNARY_NOT", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 359, + "content": "UNARY_POSITIVE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 360, + "content": "UNPACK_EX", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 361, + "content": "UNPACK_SEQUENCE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 362, + "content": "WITH_CLEANUP_FINISH", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 363, + "content": "WITH_CLEANUP_START", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 364, + "content": "WITH_EXCEPT_START", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 365, + "content": "YIELD_FROM", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 366, + "content": "YIELD_VALUE", + "lstrip": false, + "rstrip": true, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 367, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 368, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 369, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 370, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 371, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 372, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 373, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 374, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 375, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 376, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 377, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 378, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 379, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 380, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 381, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 382, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 383, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 384, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 385, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 386, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 387, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 388, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 389, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 390, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 391, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 392, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 393, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 394, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 395, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 396, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 397, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 398, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 399, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 400, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 401, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 402, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 403, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 404, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 405, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 406, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 407, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 408, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 409, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 410, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 411, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 412, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 413, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 414, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 415, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 416, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 417, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 418, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 419, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 420, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 421, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 422, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 423, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 424, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 425, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 426, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 427, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 428, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 429, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 430, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 431, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 432, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 433, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 434, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 435, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 436, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 437, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 438, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 439, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 440, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 441, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 442, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 443, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 444, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 445, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 446, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 447, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 448, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 449, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 450, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 451, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 452, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 453, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 454, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 455, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 456, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 457, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 458, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 459, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 460, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 461, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 462, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 463, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 464, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 465, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 466, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 467, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 468, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 469, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 470, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 471, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 472, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 473, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 474, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 475, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 476, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 477, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 478, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 479, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 480, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 481, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 482, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 483, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 484, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 485, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 486, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 487, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 488, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 489, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 490, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 491, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 492, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 493, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 494, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 495, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 496, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 497, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 498, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 499, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 500, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 501, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 502, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 503, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 504, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 505, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 506, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 507, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 508, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 509, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 510, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 511, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 512, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 513, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 514, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 515, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 516, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 517, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 518, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 519, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 520, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 521, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 522, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 523, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 524, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 525, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 526, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 527, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 528, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 529, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 530, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 531, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 532, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 533, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 534, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 535, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 536, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 537, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 538, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 539, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 540, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 541, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 542, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 543, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 544, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 545, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 546, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 547, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 548, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 549, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 550, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 551, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 552, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 553, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 554, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 555, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 556, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 557, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 558, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 559, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 560, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 561, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 562, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 563, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 564, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 565, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 566, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 567, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 568, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 569, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 570, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 571, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 572, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 573, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 574, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 575, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 576, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 577, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 578, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 579, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 580, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 581, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 582, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 583, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 584, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 585, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 586, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 587, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 588, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 589, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 590, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 591, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 592, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 593, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 594, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 595, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 596, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 597, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 598, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 599, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 600, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 601, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 602, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 603, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 604, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 605, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 606, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 607, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 608, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 609, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 610, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 611, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 612, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 613, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 614, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 615, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 616, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 617, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 618, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 619, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 620, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 621, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 622, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 623, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 624, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 625, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 626, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 627, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 628, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 629, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 630, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 631, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 632, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 633, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 634, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 635, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 636, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 637, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 638, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 639, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 640, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 641, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 642, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 643, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 644, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 645, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 646, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 647, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 648, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 649, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 650, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 651, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 652, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 653, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 654, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 655, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 656, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 657, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 658, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 659, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 660, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 661, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 662, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 663, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 664, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 665, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 666, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 667, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 668, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 669, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 670, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 671, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 672, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 673, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 674, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 675, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 676, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 677, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 678, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 679, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 680, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 681, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 682, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 683, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 684, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 685, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 686, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 687, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 688, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 689, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 690, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 691, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 692, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 693, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 694, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 695, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 696, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 697, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 698, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 699, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 700, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 701, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 702, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 703, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 704, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 705, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 706, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 707, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 708, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 709, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 710, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 711, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 712, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 713, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 714, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 715, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 716, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 717, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 718, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 719, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 720, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 721, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 722, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 723, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 724, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 725, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 726, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 727, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 728, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 729, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 730, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 731, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 732, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 733, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 734, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 735, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 736, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 737, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 738, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 739, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 740, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 741, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 742, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 743, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 744, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 745, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 746, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 747, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 748, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 749, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 750, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 751, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 752, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 753, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 754, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 755, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 756, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 757, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 758, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 759, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 760, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 761, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 762, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 763, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 764, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 765, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 766, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 767, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 768, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 769, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 770, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 771, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 772, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 773, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 774, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 775, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 776, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 777, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 778, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 779, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 780, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 781, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 782, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 783, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 784, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 785, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 786, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 787, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 788, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 789, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 790, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 791, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 792, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 793, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 794, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 795, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 796, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 797, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 798, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 799, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 800, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 801, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 802, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 803, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 804, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 805, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 806, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 807, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 808, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 809, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 810, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 811, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 812, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 813, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 814, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 815, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 816, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 817, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 818, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 819, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 820, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 821, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 822, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 823, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 824, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 825, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 826, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 827, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 828, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 829, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 830, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 831, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 832, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 833, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 834, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 835, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 836, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 837, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 838, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 839, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 840, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 841, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 842, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 843, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 844, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 845, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 846, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 847, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 848, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 849, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 850, + "content": "", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 851, + "content": "match", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 852, + "content": "type", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 853, + "content": "HAVE_ARGUMENT", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 854, + "content": "CALL_INTRINSIC_1", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 855, + "content": "CALL_INTRINSIC_2", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 856, + "content": "JUMP_NO_INTERRUPT", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 857, + "content": "nargs", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 858, + "content": "vargs", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 859, + "content": "compare", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 860, + "content": "name", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 861, + "content": "const", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + }, + { + "id": 862, + "content": "local", + "lstrip": false, + "rstrip": false, + "normalized": false, + "single_word": false, + "special": true + } + ], + "normalizer": null, + "pre_tokenizer": { + "type": "ByteLevel", + "add_prefix_space": false, + "trim_offsets": true, + "use_regex": true + }, + "post_processor": { + "type": "RobertaProcessing", + "sep": [ + "", + 2 + ], + "cls": [ + "", + 1 + ], + "trim_offsets": true, + "add_prefix_space": false + }, + "decoder": { + "type": "ByteLevel", + "add_prefix_space": true, + "trim_offsets": true, + "use_regex": true + }, + "model": { + "type": "BPE", + "dropout": null, + "unk_token": null, + "continuing_subword_prefix": "", + "end_of_word_suffix": "", + "fuse_unk": false, + "byte_fallback": false, + "vocab": { + "": 0, + "": 1, + "": 2, + "": 3, + "": 4, + "!": 5, + "\"": 6, + "#": 7, + "$": 8, + "%": 9, + "&": 10, + "'": 11, + "(": 12, + ")": 13, + "*": 14, + "+": 15, + ",": 16, + "-": 17, + ".": 18, + "/": 19, + "0": 20, + "1": 21, + "2": 22, + "3": 23, + "4": 24, + "5": 25, + "6": 26, + "7": 27, + "8": 28, + "9": 29, + ":": 30, + ";": 31, + "<": 32, + "=": 33, + ">": 34, + "?": 35, + "@": 36, + "A": 37, + "B": 38, + "C": 39, + "D": 40, + "E": 41, + "F": 42, + "G": 43, + "H": 44, + "I": 45, + "J": 46, + "K": 47, + "L": 48, + "M": 49, + "N": 50, + "O": 51, + "P": 52, + "Q": 53, + "R": 54, + "S": 55, + "T": 56, + "U": 57, + "V": 58, + "W": 59, + "X": 60, + "Y": 61, + "Z": 62, + "[": 63, + "\\": 64, + "]": 65, + "^": 66, + "_": 67, + "`": 68, + "a": 69, + "b": 70, + "c": 71, + "d": 72, + "e": 73, + "f": 74, + "g": 75, + "h": 76, + "i": 77, + "j": 78, + "k": 79, + "l": 80, + "m": 81, + "n": 82, + "o": 83, + "p": 84, + "q": 85, + "r": 86, + "s": 87, + "t": 88, + "u": 89, + "v": 90, + "w": 91, + "x": 92, + "y": 93, + "z": 94, + "{": 95, + "|": 96, + "}": 97, + "~": 98, + "Ġ": 99, + "-=": 100, + "<<": 101, + ">>": 102, + ":=": 103, + ">=": 104, + "<=": 105, + "==": 106, + "!=": 107, + "+=": 108, + "//=": 109, + "**=": 110, + "/=": 111, + "//": 112, + "%=": 113, + "@=": 114, + "&=": 115, + "|=": 116, + "^=": 117, + ">>=": 118, + "<<=": 119, + "*=": 120, + "()": 121, + "):": 122, + "~>>": 123, + "**": 124, + "": 126, + "E->": 127, + "": 128, + "defaults": 129, + "args:": 130, + "vararg:": 131, + "": 132, + "": 133, + "": 134, + "": 135, + "E-END": 136, + "": 137, + "~~>": 138, + "": 139, + "": 140, + "": 141, + "": 142, + "": 143, + "": 144, + "": 145, + "": 146, + "False": 147, + "None": 148, + "True": 149, + "and": 150, + "assert": 151, + "async": 152, + "await": 153, + "break": 154, + "class": 155, + "continue": 156, + "def": 157, + "del": 158, + "elif": 159, + "else": 160, + "else:": 161, + "except": 162, + "except:": 163, + "finally": 164, + "finally:": 165, + "for": 166, + "from": 167, + "global": 168, + "if": 169, + "import": 170, + "in": 171, + "is": 172, + "lambda": 173, + "nonlocal": 174, + "not": 175, + "or": 176, + "pass": 177, + "raise": 178, + "return": 179, + "try": 180, + "try:": 181, + "while": 182, + "with": 183, + "yield": 184, + "case": 185, + "as": 186, + "ASYNC_GEN_WRAP": 187, + "BEFORE_ASYNC_WITH": 188, + "BEFORE_WITH": 189, + "BEGIN_FINALLY": 190, + "BINARY_ADD": 191, + "BINARY_AND": 192, + "BINARY_FLOOR_DIVIDE": 193, + "BINARY_LSHIFT": 194, + "BINARY_MATRIX_MULTIPLY": 195, + "BINARY_MODULO": 196, + "BINARY_MULTIPLY": 197, + "BINARY_OP": 198, + "BINARY_OR": 199, + "BINARY_POWER": 200, + "BINARY_RSHIFT": 201, + "BINARY_SLICE": 202, + "BINARY_SUBSCR": 203, + "BINARY_SUBTRACT": 204, + "BINARY_TRUE_DIVIDE": 205, + "BINARY_XOR": 206, + "BREAK_LOOP": 207, + "BUILD_CONST_KEY_MAP": 208, + "BUILD_LIST": 209, + "BUILD_LIST_UNPACK": 210, + "BUILD_MAP": 211, + "BUILD_MAP_UNPACK": 212, + "BUILD_MAP_UNPACK_WITH_CALL": 213, + "BUILD_SET": 214, + "BUILD_SET_UNPACK": 215, + "BUILD_SLICE": 216, + "BUILD_STRING": 217, + "BUILD_TUPLE": 218, + "BUILD_TUPLE_UNPACK": 219, + "BUILD_TUPLE_UNPACK_WITH_CALL": 220, + "CACHE": 221, + "CALL_FINALLY": 222, + "CALL_FUNCTION": 223, + "CALL_FUNCTION_EX": 224, + "CALL_FUNCTION_KW": 225, + "CALL_METHOD": 226, + "CHECK_EG_MATCH": 227, + "CHECK_EXC_MATCH": 228, + "CLEANUP_THROW": 229, + "COMPARE_OP": 230, + "CONTAINS_OP": 231, + "CONTINUE_LOOP": 232, + "COPY": 233, + "COPY_DICT_WITHOUT_KEYS": 234, + "COPY_FREE_VARS": 235, + "DELETE_ATTR": 236, + "DELETE_DEREF": 237, + "DELETE_FAST": 238, + "DELETE_GLOBAL": 239, + "DELETE_NAME": 240, + "DELETE_SUBSCR": 241, + "DICT_MERGE": 242, + "DICT_UPDATE": 243, + "DUP_TOP": 244, + "DUP_TOP_TWO": 245, + "END_ASYNC_FOR": 246, + "END_FINALLY": 247, + "END_FOR": 248, + "END_SEND": 249, + "EXTENDED_ARG": 250, + "FOR_ITER": 251, + "FORMAT_VALUE": 252, + "GEN_START": 253, + "GET_AITER": 254, + "GET_ANEXT": 255, + "GET_AWAITABLE": 256, + "GET_ITER": 257, + "GET_LEN": 258, + "GET_YIELD_FROM_ITER": 259, + "IMPORT_FROM": 260, + "IMPORT_NAME": 261, + "IMPORT_STAR": 262, + "INPLACE_ADD": 263, + "INPLACE_AND": 264, + "INPLACE_FLOOR_DIVIDE": 265, + "INPLACE_LSHIFT": 266, + "INPLACE_MATRIX_MULTIPLY": 267, + "INPLACE_MODULO": 268, + "INPLACE_MULTIPLY": 269, + "INPLACE_OR": 270, + "INPLACE_POWER": 271, + "INPLACE_RSHIFT": 272, + "INPLACE_SUBTRACT": 273, + "INPLACE_TRUE_DIVIDE": 274, + "INPLACE_XOR": 275, + "INTERPRETER_EXIT": 276, + "IS_OP": 277, + "JUMP_ABSOLUTE": 278, + "JUMP_BACKWARD": 279, + "JUMP_BACKWARD_NO_INTERRUPT": 280, + "JUMP_FORWARD": 281, + "JUMP_IF_FALSE_OR_POP": 282, + "JUMP_IF_NOT_EXC_MATCH": 283, + "JUMP_IF_TRUE_OR_POP": 284, + "LIST_APPEND": 285, + "LIST_EXTEND": 286, + "LIST_TO_TUPLE": 287, + "LOAD_ASSERTION_ERROR": 288, + "LOAD_ATTR": 289, + "LOAD_BUILD_CLASS": 290, + "LOAD_CLASSDEREF": 291, + "LOAD_CLOSURE": 292, + "LOAD_CONST": 293, + "LOAD_DEREF": 294, + "LOAD_FAST": 295, + "LOAD_FAST_AND_CLEAR": 296, + "LOAD_FAST_CHECK": 297, + "LOAD_GLOBAL": 298, + "LOAD_LOCALS": 299, + "LOAD_METHOD": 300, + "LOAD_NAME": 301, + "LOAD_SUPER_ATTR": 302, + "MAKE_CELL": 303, + "MAKE_FUNCTION": 304, + "MAP_ADD": 305, + "MATCH_CLASS": 306, + "MATCH_KEYS": 307, + "MATCH_MAPPING": 308, + "MATCH_SEQUENCE": 309, + "NOP": 310, + "POP_BLOCK": 311, + "POP_EXCEPT": 312, + "POP_FINALLY": 313, + "POP_JUMP_FORWARD_IF_FALSE": 314, + "POP_JUMP_FORWARD_IF_NONE": 315, + "POP_JUMP_FORWARD_IF_NOT_NONE": 316, + "POP_JUMP_FORWARD_IF_TRUE": 317, + "POP_JUMP_IF_FALSE": 318, + "POP_JUMP_IF_NONE": 319, + "POP_JUMP_IF_NOT_NONE": 320, + "POP_JUMP_IF_TRUE": 321, + "POP_TOP": 322, + "PRECALL": 323, + "PREP_RERAISE_STAR": 324, + "PRINT_EXPR": 325, + "PUSH_EXC_INFO": 326, + "PUSH_NULL": 327, + "RAISE_VARARGS": 328, + "RERAISE": 329, + "RESERVED": 330, + "RESUME": 331, + "RETURN_CONST": 332, + "RETURN_GENERATOR": 333, + "RETURN_VALUE": 334, + "ROT_FOUR": 335, + "ROT_N": 336, + "ROT_THREE": 337, + "ROT_TWO": 338, + "SEND": 339, + "SET_ADD": 340, + "SET_UPDATE": 341, + "SETUP_ANNOTATIONS": 342, + "SETUP_ASYNC_WITH": 343, + "SETUP_EXCEPT": 344, + "SETUP_FINALLY": 345, + "SETUP_LOOP": 346, + "SETUP_WITH": 347, + "STORE_ATTR": 348, + "STORE_DEREF": 349, + "STORE_FAST": 350, + "STORE_GLOBAL": 351, + "STORE_NAME": 352, + "STORE_SLICE": 353, + "STORE_SUBSCR": 354, + "SWAP": 355, + "UNARY_INVERT": 356, + "UNARY_NEGATIVE": 357, + "UNARY_NOT": 358, + "UNARY_POSITIVE": 359, + "UNPACK_EX": 360, + "UNPACK_SEQUENCE": 361, + "WITH_CLEANUP_FINISH": 362, + "WITH_CLEANUP_START": 363, + "WITH_EXCEPT_START": 364, + "YIELD_FROM": 365, + "YIELD_VALUE": 366, + "": 367, + "": 368, + "": 369, + "": 370, + "": 371, + "": 372, + "": 373, + "": 374, + "": 375, + "": 376, + "": 377, + "": 378, + "": 379, + "": 380, + "": 381, + "": 382, + "": 383, + "": 384, + "": 385, + "": 386, + "": 387, + "": 388, + "": 389, + "": 390, + "": 391, + "": 392, + "": 393, + "": 394, + "": 395, + "": 396, + "": 397, + "": 398, + "": 399, + "": 400, + "": 401, + "": 402, + "": 403, + "": 404, + "": 405, + "": 406, + "": 407, + "": 408, + "": 409, + "": 410, + "": 411, + "": 412, + "": 413, + "": 414, + "": 415, + "": 416, + "": 417, + "": 418, + "": 419, + "": 420, + "": 421, + "": 422, + "": 423, + "": 424, + "": 425, + "": 426, + "": 427, + "": 428, + "": 429, + "": 430, + "": 431, + "": 432, + "": 433, + "": 434, + "": 435, + "": 436, + "": 437, + "": 438, + "": 439, + "": 440, + "": 441, + "": 442, + "": 443, + "": 444, + "": 445, + "": 446, + "": 447, + "": 448, + "": 449, + "": 450, + "": 451, + "": 452, + "": 453, + "": 454, + "": 455, + "": 456, + "": 457, + "": 458, + "": 459, + "": 460, + "": 461, + "": 462, + "": 463, + "": 464, + "": 465, + "": 466, + "": 467, + "": 468, + "": 469, + "": 470, + "": 471, + "": 472, + "": 473, + "": 474, + "": 475, + "": 476, + "": 477, + "": 478, + "": 479, + "": 480, + "": 481, + "": 482, + "": 483, + "": 484, + "": 485, + "": 486, + "": 487, + "": 488, + "": 489, + "": 490, + "": 491, + "": 492, + "": 493, + "": 494, + "": 495, + "": 496, + "": 497, + "": 498, + "": 499, + "": 500, + "": 501, + "": 502, + "": 503, + "": 504, + "": 505, + "": 506, + "": 507, + "": 508, + "": 509, + "": 510, + "": 511, + "": 512, + "": 513, + "": 514, + "": 515, + "": 516, + "": 517, + "": 518, + "": 519, + "": 520, + "": 521, + "": 522, + "": 523, + "": 524, + "": 525, + "": 526, + "": 527, + "": 528, + "": 529, + "": 530, + "": 531, + "": 532, + "": 533, + "": 534, + "": 535, + "": 536, + "": 537, + "": 538, + "": 539, + "": 540, + "": 541, + "": 542, + "": 543, + "": 544, + "": 545, + "": 546, + "": 547, + "": 548, + "": 549, + "": 550, + "": 551, + "": 552, + "": 553, + "": 554, + "": 555, + "": 556, + "": 557, + "": 558, + "": 559, + "": 560, + "": 561, + "": 562, + "": 563, + "": 564, + "": 565, + "": 566, + "": 567, + "": 568, + "": 569, + "": 570, + "": 571, + "": 572, + "": 573, + "": 574, + "": 575, + "": 576, + "": 577, + "": 578, + "": 579, + "": 580, + "": 581, + "": 582, + "": 583, + "": 584, + "": 585, + "": 586, + "": 587, + "": 588, + "": 589, + "": 590, + "": 591, + "": 592, + "": 593, + "": 594, + "": 595, + "": 596, + "": 597, + "": 598, + "": 599, + "": 600, + "": 601, + "": 602, + "": 603, + "": 604, + "": 605, + "": 606, + "": 607, + "": 608, + "": 609, + "": 610, + "": 611, + "": 612, + "": 613, + "": 614, + "": 615, + "": 616, + "": 617, + "": 618, + "": 619, + "": 620, + "": 621, + "": 622, + "": 623, + "": 624, + "": 625, + "": 626, + "": 627, + "": 628, + "": 629, + "": 630, + "": 631, + "": 632, + "": 633, + "": 634, + "": 635, + "": 636, + "": 637, + "": 638, + "": 639, + "": 640, + "": 641, + "": 642, + "": 643, + "": 644, + "": 645, + "": 646, + "": 647, + "": 648, + "": 649, + "": 650, + "": 651, + "": 652, + "": 653, + "": 654, + "": 655, + "": 656, + "": 657, + "": 658, + "": 659, + "": 660, + "": 661, + "": 662, + "": 663, + "": 664, + "": 665, + "": 666, + "": 667, + "": 668, + "": 669, + "": 670, + "": 671, + "": 672, + "": 673, + "": 674, + "": 675, + "": 676, + "": 677, + "": 678, + "": 679, + "": 680, + "": 681, + "": 682, + "": 683, + "": 684, + "": 685, + "": 686, + "": 687, + "": 688, + "": 689, + "": 690, + "": 691, + "": 692, + "": 693, + "": 694, + "": 695, + "": 696, + "": 697, + "": 698, + "": 699, + "": 700, + "": 701, + "": 702, + "": 703, + "": 704, + "": 705, + "": 706, + "": 707, + "": 708, + "": 709, + "": 710, + "": 711, + "": 712, + "": 713, + "": 714, + "": 715, + "": 716, + "": 717, + "": 718, + "": 719, + "": 720, + "": 721, + "": 722, + "": 723, + "": 724, + "": 725, + "": 726, + "": 727, + "": 728, + "": 729, + "": 730, + "": 731, + "": 732, + "": 733, + "": 734, + "": 735, + "": 736, + "": 737, + "": 738, + "": 739, + "": 740, + "": 741, + "": 742, + "": 743, + "": 744, + "": 745, + "": 746, + "": 747, + "": 748, + "": 749, + "": 750, + "": 751, + "": 752, + "": 753, + "": 754, + "": 755, + "": 756, + "": 757, + "": 758, + "": 759, + "": 760, + "": 761, + "": 762, + "": 763, + "": 764, + "": 765, + "": 766, + "": 767, + "": 768, + "": 769, + "": 770, + "": 771, + "": 772, + "": 773, + "": 774, + "": 775, + "": 776, + "": 777, + "": 778, + "": 779, + "": 780, + "": 781, + "": 782, + "": 783, + "": 784, + "": 785, + "": 786, + "": 787, + "": 788, + "": 789, + "": 790, + "": 791, + "": 792, + "": 793, + "": 794, + "": 795, + "": 796, + "": 797, + "": 798, + "": 799, + "": 800, + "": 801, + "": 802, + "": 803, + "": 804, + "": 805, + "": 806, + "": 807, + "": 808, + "": 809, + "": 810, + "": 811, + "": 812, + "": 813, + "": 814, + "": 815, + "": 816, + "": 817, + "": 818, + "": 819, + "": 820, + "": 821, + "": 822, + "": 823, + "": 824, + "": 825, + "": 826, + "": 827, + "": 828, + "": 829, + "": 830, + "": 831, + "": 832, + "": 833, + "": 834, + "": 835, + "": 836, + "": 837, + "": 838, + "": 839, + "": 840, + "": 841, + "": 842, + "": 843, + "": 844, + "": 845, + "": 846, + "": 847, + "": 848, + "": 849, + "": 850, + "match": 851, + "type": 852, + "HAVE_ARGUMENT": 853, + "CALL_INTRINSIC_1": 854, + "CALL_INTRINSIC_2": 855, + "JUMP_NO_INTERRUPT": 856, + "nargs": 857, + "vargs": 858, + "compare": 859, + "name": 860, + "const": 861, + "local": 862 + }, + "merges": [] + } +} diff --git a/model_training/statement/tokenizer/tokenizer_config.json b/model_training/statement/tokenizer/tokenizer_config.json new file mode 100644 index 0000000..edcef60 --- /dev/null +++ b/model_training/statement/tokenizer/tokenizer_config.json @@ -0,0 +1,929 @@ +{ + "add_prefix_space": false, + "additional_special_tokens": [ + "", + "", + "", + "", + "", + "!", + "\"", + "#", + "$", + "%", + "&", + "'", + "(", + ")", + "*", + "+", + ",", + "-", + ".", + "/", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + ":", + ";", + "<", + "=", + ">", + "?", + "@", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "[", + "\\", + "]", + "^", + "_", + "`", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "{", + "|", + "}", + "~", + "Ġ", + "-=", + "<<", + ">>", + ":=", + ">=", + "<=", + "==", + "!=", + "+=", + "//=", + "**=", + "/=", + "//", + "%=", + "@=", + "&=", + "|=", + "^=", + ">>=", + "<<=", + "*=", + "()", + "):", + "~>>", + "**", + "", + "E->", + "", + "defaults", + "args:", + "vararg:", + "", + "", + "", + "", + "E-END", + "", + "~~>", + "", + "", + "", + "", + "", + "", + "", + "", + "False", + "None", + "True", + "and", + "assert", + "async", + "await", + "break", + "class", + "continue", + "def", + "del", + "elif", + "else", + "else:", + "except", + "except:", + "finally", + "finally:", + "for", + "from", + "global", + "if", + "import", + "in", + "is", + "lambda", + "nonlocal", + "not", + "or", + "pass", + "raise", + "return", + "try", + "try:", + "while", + "with", + "yield", + "case", + "as", + "ASYNC_GEN_WRAP", + "BEFORE_ASYNC_WITH", + "BEFORE_WITH", + "BEGIN_FINALLY", + "BINARY_ADD", + "BINARY_AND", + "BINARY_FLOOR_DIVIDE", + "BINARY_LSHIFT", + "BINARY_MATRIX_MULTIPLY", + "BINARY_MODULO", + "BINARY_MULTIPLY", + "BINARY_OP", + "BINARY_OR", + "BINARY_POWER", + "BINARY_RSHIFT", + "BINARY_SLICE", + "BINARY_SUBSCR", + "BINARY_SUBTRACT", + "BINARY_TRUE_DIVIDE", + "BINARY_XOR", + "BREAK_LOOP", + "BUILD_CONST_KEY_MAP", + "BUILD_LIST", + "BUILD_LIST_UNPACK", + "BUILD_MAP", + "BUILD_MAP_UNPACK", + "BUILD_MAP_UNPACK_WITH_CALL", + "BUILD_SET", + "BUILD_SET_UNPACK", + "BUILD_SLICE", + "BUILD_STRING", + "BUILD_TUPLE", + "BUILD_TUPLE_UNPACK", + "BUILD_TUPLE_UNPACK_WITH_CALL", + "CACHE", + "CALL_FINALLY", + "CALL_FUNCTION", + "CALL_FUNCTION_EX", + "CALL_FUNCTION_KW", + "CALL_METHOD", + "CHECK_EG_MATCH", + "CHECK_EXC_MATCH", + "CLEANUP_THROW", + "COMPARE_OP", + "CONTAINS_OP", + "CONTINUE_LOOP", + "COPY", + "COPY_DICT_WITHOUT_KEYS", + "COPY_FREE_VARS", + "DELETE_ATTR", + "DELETE_DEREF", + "DELETE_FAST", + "DELETE_GLOBAL", + "DELETE_NAME", + "DELETE_SUBSCR", + "DICT_MERGE", + "DICT_UPDATE", + "DUP_TOP", + "DUP_TOP_TWO", + "END_ASYNC_FOR", + "END_FINALLY", + "END_FOR", + "END_SEND", + "EXTENDED_ARG", + "FOR_ITER", + "FORMAT_VALUE", + "GEN_START", + "GET_AITER", + "GET_ANEXT", + "GET_AWAITABLE", + "GET_ITER", + "GET_LEN", + "GET_YIELD_FROM_ITER", + "IMPORT_FROM", + "IMPORT_NAME", + "IMPORT_STAR", + "INPLACE_ADD", + "INPLACE_AND", + "INPLACE_FLOOR_DIVIDE", + "INPLACE_LSHIFT", + "INPLACE_MATRIX_MULTIPLY", + "INPLACE_MODULO", + "INPLACE_MULTIPLY", + "INPLACE_OR", + "INPLACE_POWER", + "INPLACE_RSHIFT", + "INPLACE_SUBTRACT", + "INPLACE_TRUE_DIVIDE", + "INPLACE_XOR", + "INTERPRETER_EXIT", + "IS_OP", + "JUMP_ABSOLUTE", + "JUMP_BACKWARD", + "JUMP_BACKWARD_NO_INTERRUPT", + "JUMP_FORWARD", + "JUMP_IF_FALSE_OR_POP", + "JUMP_IF_NOT_EXC_MATCH", + "JUMP_IF_TRUE_OR_POP", + "LIST_APPEND", + "LIST_EXTEND", + "LIST_TO_TUPLE", + "LOAD_ASSERTION_ERROR", + "LOAD_ATTR", + "LOAD_BUILD_CLASS", + "LOAD_CLASSDEREF", + "LOAD_CLOSURE", + "LOAD_CONST", + "LOAD_DEREF", + "LOAD_FAST", + "LOAD_FAST_AND_CLEAR", + "LOAD_FAST_CHECK", + "LOAD_GLOBAL", + "LOAD_LOCALS", + "LOAD_METHOD", + "LOAD_NAME", + "LOAD_SUPER_ATTR", + "MAKE_CELL", + "MAKE_FUNCTION", + "MAP_ADD", + "MATCH_CLASS", + "MATCH_KEYS", + "MATCH_MAPPING", + "MATCH_SEQUENCE", + "NOP", + "POP_BLOCK", + "POP_EXCEPT", + "POP_FINALLY", + "POP_JUMP_FORWARD_IF_FALSE", + "POP_JUMP_FORWARD_IF_NONE", + "POP_JUMP_FORWARD_IF_NOT_NONE", + "POP_JUMP_FORWARD_IF_TRUE", + "POP_JUMP_IF_FALSE", + "POP_JUMP_IF_NONE", + "POP_JUMP_IF_NOT_NONE", + "POP_JUMP_IF_TRUE", + "POP_TOP", + "PRECALL", + "PREP_RERAISE_STAR", + "PRINT_EXPR", + "PUSH_EXC_INFO", + "PUSH_NULL", + "RAISE_VARARGS", + "RERAISE", + "RESERVED", + "RESUME", + "RETURN_CONST", + "RETURN_GENERATOR", + "RETURN_VALUE", + "ROT_FOUR", + "ROT_N", + "ROT_THREE", + "ROT_TWO", + "SEND", + "SET_ADD", + "SET_UPDATE", + "SETUP_ANNOTATIONS", + "SETUP_ASYNC_WITH", + "SETUP_EXCEPT", + "SETUP_FINALLY", + "SETUP_LOOP", + "SETUP_WITH", + "STORE_ATTR", + "STORE_DEREF", + "STORE_FAST", + "STORE_GLOBAL", + "STORE_NAME", + "STORE_SLICE", + "STORE_SUBSCR", + "SWAP", + "UNARY_INVERT", + "UNARY_NEGATIVE", + "UNARY_NOT", + "UNARY_POSITIVE", + "UNPACK_EX", + "UNPACK_SEQUENCE", + "WITH_CLEANUP_FINISH", + "WITH_CLEANUP_START", + "WITH_EXCEPT_START", + "YIELD_FROM", + "YIELD_VALUE", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "match", + "type", + "HAVE_ARGUMENT", + "CALL_INTRINSIC_1", + "CALL_INTRINSIC_2", + "JUMP_NO_INTERRUPT", + "nargs", + "vargs", + "compare", + "name", + "const", + "local" + ], + "bos_token": { + "__type": "AddedToken", + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "clean_up_tokenization_spaces": true, + "cls_token": { + "__type": "AddedToken", + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "eos_token": { + "__type": "AddedToken", + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "errors": "replace", + "mask_token": { + "__type": "AddedToken", + "content": "", + "lstrip": true, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "model_max_length": 512, + "pad_token": { + "__type": "AddedToken", + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "sep_token": { + "__type": "AddedToken", + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + }, + "tokenizer_class": "RobertaTokenizer", + "trim_offsets": true, + "unk_token": { + "__type": "AddedToken", + "content": "", + "lstrip": false, + "normalized": true, + "rstrip": false, + "single_word": false + } +} diff --git a/model_training/statement/train_seq2seq.py b/model_training/statement/train_seq2seq.py new file mode 100644 index 0000000..0127603 --- /dev/null +++ b/model_training/statement/train_seq2seq.py @@ -0,0 +1,93 @@ +import os +import pathlib +import time +from datetime import timedelta +import click + +from datasets import ReadInstruction, load_dataset +from StatementConfiguration import StatementConfiguration, parse_statement_config_json +from transformers import ( + DataCollatorForSeq2Seq, + RobertaTokenizer, + Seq2SeqTrainer, + Seq2SeqTrainingArguments, + T5ForConditionalGeneration, +) + + +def load_tokenized_train_dataset(dataset_repo_name: str, dataset_percentage: int): + # Load the tokenized dataset + tokenized_train_dataset = load_dataset( + dataset_repo_name, + token=True, + split=ReadInstruction("train", to=dataset_percentage, unit="%"), + ) + return tokenized_train_dataset + + +def train_statement_model(config: StatementConfiguration): + # load model, Salesforce/codet5-base is a pretrained model solving the code generation task. + tokenizer = RobertaTokenizer.from_pretrained(config.tokenizer_repo_name) + model = T5ForConditionalGeneration.from_pretrained(config.pretrained_seq2seq_repo_name) + data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) + + model_dir = str(config.statement_model_dir) + model_repo_name = config.statement_model_repo_name + + train_args = Seq2SeqTrainingArguments( + output_dir=model_dir, + learning_rate=config.statement_training_parameters.learning_rate, + per_device_train_batch_size=config.statement_training_parameters.batch_size, + per_device_eval_batch_size=config.statement_training_parameters.batch_size, + weight_decay=0.01, + fp16=config.fp16, + logging_dir=str(config.log_dir), + report_to="tensorboard", + logging_strategy="steps", + logging_steps=1000, + save_strategy="steps", + save_steps=10000, + save_total_limit=2, + num_train_epochs=config.statement_training_parameters.epochs, + predict_with_generate=True, + push_to_hub=True, + hub_model_id=model_repo_name, + hub_private_repo=True, + ddp_backend="nccl", + ddp_find_unused_parameters=False, + ) + + tokenized_train_dataset = load_tokenized_train_dataset(config.tokenized_dataset_repo_name, config.dataset_percentage) + trainer = Seq2SeqTrainer( + model=model, + args=train_args, + data_collator=data_collator, + train_dataset=tokenized_train_dataset, + tokenizer=tokenizer, + ) + + start = time.time() + trainer.train() + duration = str(timedelta(seconds=time.time() - start)) + + if int(os.environ["LOCAL_RANK"]) == 0: + # upload the latest version of the model to the Model Hub on Huggingface + trainer.save_model(str(config.statement_model_dir)) + # this command returns the URL of the commit it just did + trainer.push_to_hub( + commit_message=duration, + finetuned_from=config.pretrained_seq2seq_repo_name, + dataset=config.tokenized_dataset_repo_name, + ) + + +@click.command(help="Training script for the statement translation model given a statement json.") +@click.argument("json_path", type=str) +def main(json_path: str): + json_file_path = pathlib.Path(json_path) + statement_config = parse_statement_config_json(json_file_path) + train_statement_model(statement_config) + + +if __name__ == "__main__": + main() diff --git a/model_training/statement/train_tokenizer_auto.py b/model_training/statement/train_tokenizer_auto.py new file mode 100644 index 0000000..54afea9 --- /dev/null +++ b/model_training/statement/train_tokenizer_auto.py @@ -0,0 +1,93 @@ +import logging +import pathlib +import click + +from datasets import ReadInstruction, load_dataset +from huggingface_hub import HfApi, repo_exists +from StatementConfiguration import StatementConfiguration, parse_statement_config_json +from tokenizers import Tokenizer +from transformers import AutoTokenizer + + +def get_untrained_tokenizer(tokenizer_repo_name: str) -> AutoTokenizer: + tokenizer_dir = pathlib.Path(__file__).parent / tokenizer_repo_name + tokenizer = AutoTokenizer.from_pretrained(tokenizer_dir) + return tokenizer + + +def save_and_upload_tokenizer( + tokenizer: Tokenizer, + tokenizer_json_path: pathlib.Path, + tokenizer_repo_name: str, + dataset_name: str, +): + # Save the tokenizer locally + tokenizer.save_pretrained(str(tokenizer_json_path.parent.resolve())) + + # Upload files to Hugging Face Hub + api = HfApi() + api.create_repo(tokenizer_repo_name, exist_ok=True, private=True) + api.upload_file( + path_in_repo="tokenizer_config.json", + path_or_fileobj=str(tokenizer_json_path.parent / "tokenizer_config.json"), + repo_id=tokenizer_repo_name, + commit_message=f"Trained tokenizer using {dataset_name}", + ) + api.upload_file( + path_in_repo="vocab.json", + path_or_fileobj=str(tokenizer_json_path.parent / "vocab.json"), + repo_id=tokenizer_repo_name, + commit_message="Extracted vocabulary from tokenizer", + ) + api.upload_file( + path_in_repo="merges.txt", + path_or_fileobj=str(tokenizer_json_path.parent / "merges.txt"), + repo_id=tokenizer_repo_name, + commit_message="Extracted merges from tokenizer", + ) + api.upload_file( + path_in_repo="tokenizer.json", + path_or_fileobj=str(tokenizer_json_path.parent / "tokenizer.json"), + repo_id=tokenizer_repo_name, + commit_message="Extracted tokenizer", + ) + api.upload_file( + path_in_repo="special_tokens_map.json", + path_or_fileobj=str(tokenizer_json_path.parent / "special_tokens_map.json"), + repo_id=tokenizer_repo_name, + commit_message="Extracted special tokens map", + ) + + +def train_tokenizer(config: StatementConfiguration, tokenizer_json_path: pathlib.Path): + if repo_exists(config.base_repo_name): + logging.error(f"{config.base_repo_name} has already exists") + exit(1) + + tokenizer = get_untrained_tokenizer("tokenizer") + + train_dataset = load_dataset( + config.dataset_repo_name, + token=True, + split=ReadInstruction("train", to=config.dataset_percentage, unit="%"), + )["bytecode"] + + tokenizer = tokenizer.train_new_from_iterator(train_dataset, vocab_size=30000) + save_and_upload_tokenizer( + tokenizer, + tokenizer_json_path, + config.tokenizer_repo_name, + config.dataset_repo_name, + ) + + +@click.command(help="Training script for the bytecode tokenizer for the statement model given a statement json.") +@click.argument("json_path", type=str) +def main(json_path: str): + json_file_path = pathlib.Path(json_path) + statement_config = parse_statement_config_json(json_file_path) + train_tokenizer(statement_config, json_file_path) + + +if __name__ == "__main__": + main() diff --git a/model_training/train_models.py b/model_training/train_models.py new file mode 100644 index 0000000..9dae8c6 --- /dev/null +++ b/model_training/train_models.py @@ -0,0 +1,117 @@ +import logging +import os +import pathlib +import subprocess +import click + +from pylingual.utils.get_logger import get_logger + + +def train_segmentation(segmentation_config_path: pathlib.Path, logger: logging.Logger, nnodes: int = 1, nproc_per_node: int = 1, rdzv_port: int = 29400): + segmentation_root = pathlib.Path(__file__).parent / "segmentation" + + # train tokenizer + logger.info("training tokenizer...") + subprocess.run(["python", segmentation_root / "train_tokenizer.py", segmentation_config_path]) + + # train mlm (single gpu to avoid conflicts with local tokenized data) + logger.info("training masked language model...") + subprocess.run( + [ + "torchrun", + f"--nnodes={nnodes}", + f"--nproc-per-node={nproc_per_node}", + "--rdzv-backend=c10d", + f"--rdzv-endpoint=localhost:{rdzv_port}", + segmentation_root / "train_mlm.py", + segmentation_config_path, + ], + env=dict(os.environ, NCCL_P2P_DISABLE="1"), + ) + + # tokenize dataset + logger.info("tokenizing segmentation dataset...") + subprocess.run(["python", segmentation_root / "tokenize_seg.py", segmentation_config_path]) + + # train segmentation model (4 gpus) + logger.info("training segmentation model...") + subprocess.run( + [ + "torchrun", + f"--nnodes={nnodes}", + f"--nproc-per-node={nproc_per_node}", + "--rdzv-backend=c10d", + f"--rdzv-endpoint=localhost:{rdzv_port}", + segmentation_root / "train_seg.py", + segmentation_config_path, + ], + env=dict(os.environ, NCCL_P2P_DISABLE="1"), + ) + + +def train_statement(statement_config_path: pathlib.Path, logger: logging.Logger, nnodes: int = 1, nproc_per_node: int = 1, rdzv_port: int = 29400): + statement_root = pathlib.Path(__file__).parent / "statement" + + # manual tokenizer + subprocess.run(["python", statement_root / "train_tokenizer_auto.py", statement_config_path]) + + # tokenize statement dataset with salesforce tokenizer + logger.info("tokenizing statement dataset...") + subprocess.run(["python", statement_root / "tokenize_seq2seq.py", statement_config_path]) + + # train statement model (4 gpus) + logger.info("training statement model...") + subprocess.run( + [ + "torchrun", + f"--nnodes={nnodes}", + f"--nproc-per-node={nproc_per_node}", + "--rdzv-backend=c10d", + f"--rdzv-endpoint=localhost:{rdzv_port}", + statement_root / "train_seq2seq.py", + statement_config_path, + ], + env=dict(os.environ, NCCL_P2P_DISABLE="1"), + ) + + +@click.command(help="Full tokenization and training pipeline for the segmentation and statement translation models.") +@click.option("--segmentation", type=str, default=None, help="The path to the segmentation model description JSON file.") +@click.option("--statement", type=str, default=None, help="The path to the statement model description JSON file.") +@click.option("--nnodes", type=int, default=1, help="Torchrun nnodes arg") +@click.option("--nproc_per_node", type=int, default=1, help="Torchrun nproc_per_node arg") +@click.option("--rdzv_port", "-p", type=int, default=29400, help="Port to use for torchrun rendezvous endpoint") +def main(segmentation: str, statement: str, nnodes: int, nproc_per_node: int, rdzv_port: int): + logger = get_logger("train-models") + + ### LOAD JSON + logger.info("Training pipeline starting...") + logger.info("Loading dataset description JSON files...") + + ### CONFIG_PATHS + segmentation_config_path = pathlib.Path(segmentation).resolve() if segmentation is not None else None + statement_config_path = pathlib.Path(statement).resolve() if statement is not None else None + + logger.info("Dataset description JSON files loaded!") + + ### TRAIN SEGMENTATION + if segmentation_config_path is not None: + logger.info("Segmentation model training starting...") + train_segmentation(segmentation_config_path, logger, nnodes, nproc_per_node, rdzv_port) + logger.info("Segmentation model training complete!") + else: + logger.warning("Segmentation model configuration json path not provided in --segmentation; skipping segmentation model training...") + + ### TRAIN STATEMENT + if statement_config_path is not None: + logger.info("Statement model training starting...") + train_statement(statement_config_path, logger, nnodes, nproc_per_node, rdzv_port) + logger.info("Statement model training complete!") + else: + logger.warning("Statement model configuration json path not provided in --statement; skipping statement model training...") + + logger.info("Training pipeline complete!") + + +if __name__ == "__main__": + main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..0694adb --- /dev/null +++ b/poetry.lock @@ -0,0 +1,3044 @@ +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. + +[[package]] +name = "accelerate" +version = "1.4.0" +description = "Accelerate" +optional = false +python-versions = ">=3.9.0" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "accelerate-1.4.0-py3-none-any.whl", hash = "sha256:f6e1e7dfaf9d799a20a1dc45efbf4b1546163eac133faa5acd0d89177c896e55"}, + {file = "accelerate-1.4.0.tar.gz", hash = "sha256:37d413e1b64cb8681ccd2908ae211cf73e13e6e636a2f598a96eccaa538773a5"}, +] + +[package.dependencies] +huggingface-hub = ">=0.21.0" +numpy = ">=1.17,<3.0.0" +packaging = ">=20.0" +psutil = "*" +pyyaml = "*" +safetensors = ">=0.4.3" +torch = ">=2.0.0" + +[package.extras] +deepspeed = ["deepspeed"] +dev = ["bitsandbytes", "black (>=23.1,<24.0)", "datasets", "diffusers", "evaluate", "hf-doc-builder (>=0.3.0)", "parameterized", "pytest (>=7.2.0,<=8.0.0)", "pytest-subtests", "pytest-xdist", "rich", "ruff (>=0.6.4,<0.7.0)", "scikit-learn", "scipy", "timm", "torchdata (>=0.8.0)", "torchpippy (>=0.2.0)", "tqdm", "transformers"] +quality = ["black (>=23.1,<24.0)", "hf-doc-builder (>=0.3.0)", "ruff (>=0.6.4,<0.7.0)"] +rich = ["rich"] +sagemaker = ["sagemaker"] +test-dev = ["bitsandbytes", "datasets", "diffusers", "evaluate", "scikit-learn", "scipy", "timm", "torchdata (>=0.8.0)", "torchpippy (>=0.2.0)", "tqdm", "transformers"] +test-prod = ["parameterized", "pytest (>=7.2.0,<=8.0.0)", "pytest-subtests", "pytest-xdist"] +test-trackers = ["comet-ml", "dvclive", "tensorboard", "wandb"] +testing = ["bitsandbytes", "datasets", "diffusers", "evaluate", "parameterized", "pytest (>=7.2.0,<=8.0.0)", "pytest-subtests", "pytest-xdist", "scikit-learn", "scipy", "timm", "torchdata (>=0.8.0)", "torchpippy (>=0.2.0)", "tqdm", "transformers"] + +[[package]] +name = "aiohappyeyeballs" +version = "2.5.0" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "aiohappyeyeballs-2.5.0-py3-none-any.whl", hash = "sha256:0850b580748c7071db98bffff6d4c94028d0d3035acc20fd721a0ce7e8cac35d"}, + {file = "aiohappyeyeballs-2.5.0.tar.gz", hash = "sha256:18fde6204a76deeabc97c48bdd01d5801cfda5d6b9c8bbeb1aaaee9d648ca191"}, +] + +[[package]] +name = "aiohttp" +version = "3.11.13" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "aiohttp-3.11.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a4fe27dbbeec445e6e1291e61d61eb212ee9fed6e47998b27de71d70d3e8777d"}, + {file = "aiohttp-3.11.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e64ca2dbea28807f8484c13f684a2f761e69ba2640ec49dacd342763cc265ef"}, + {file = "aiohttp-3.11.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9840be675de208d1f68f84d578eaa4d1a36eee70b16ae31ab933520c49ba1325"}, + {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28a772757c9067e2aee8a6b2b425d0efaa628c264d6416d283694c3d86da7689"}, + {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b88aca5adbf4625e11118df45acac29616b425833c3be7a05ef63a6a4017bfdb"}, + {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce10ddfbe26ed5856d6902162f71b8fe08545380570a885b4ab56aecfdcb07f4"}, + {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa48dac27f41b36735c807d1ab093a8386701bbf00eb6b89a0f69d9fa26b3671"}, + {file = "aiohttp-3.11.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89ce611b1eac93ce2ade68f1470889e0173d606de20c85a012bfa24be96cf867"}, + {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:78e4dd9c34ec7b8b121854eb5342bac8b02aa03075ae8618b6210a06bbb8a115"}, + {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:66047eacbc73e6fe2462b77ce39fc170ab51235caf331e735eae91c95e6a11e4"}, + {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ad8f1c19fe277eeb8bc45741c6d60ddd11d705c12a4d8ee17546acff98e0802"}, + {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64815c6f02e8506b10113ddbc6b196f58dbef135751cc7c32136df27b736db09"}, + {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:967b93f21b426f23ca37329230d5bd122f25516ae2f24a9cea95a30023ff8283"}, + {file = "aiohttp-3.11.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf1f31f83d16ec344136359001c5e871915c6ab685a3d8dee38e2961b4c81730"}, + {file = "aiohttp-3.11.13-cp310-cp310-win32.whl", hash = "sha256:00c8ac69e259c60976aa2edae3f13d9991cf079aaa4d3cd5a49168ae3748dee3"}, + {file = "aiohttp-3.11.13-cp310-cp310-win_amd64.whl", hash = "sha256:90d571c98d19a8b6e793b34aa4df4cee1e8fe2862d65cc49185a3a3d0a1a3996"}, + {file = "aiohttp-3.11.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b35aab22419ba45f8fc290d0010898de7a6ad131e468ffa3922b1b0b24e9d2e"}, + {file = "aiohttp-3.11.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81cba651db8795f688c589dd11a4fbb834f2e59bbf9bb50908be36e416dc760"}, + {file = "aiohttp-3.11.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f55d0f242c2d1fcdf802c8fabcff25a9d85550a4cf3a9cf5f2a6b5742c992839"}, + {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4bea08a6aad9195ac9b1be6b0c7e8a702a9cec57ce6b713698b4a5afa9c2e33"}, + {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6070bcf2173a7146bb9e4735b3c62b2accba459a6eae44deea0eb23e0035a23"}, + {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:718d5deb678bc4b9d575bfe83a59270861417da071ab44542d0fcb6faa686636"}, + {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f6b2c5b4a4d22b8fb2c92ac98e0747f5f195e8e9448bfb7404cd77e7bfa243f"}, + {file = "aiohttp-3.11.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747ec46290107a490d21fe1ff4183bef8022b848cf9516970cb31de6d9460088"}, + {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:01816f07c9cc9d80f858615b1365f8319d6a5fd079cd668cc58e15aafbc76a54"}, + {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a08ad95fcbd595803e0c4280671d808eb170a64ca3f2980dd38e7a72ed8d1fea"}, + {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c97be90d70f7db3aa041d720bfb95f4869d6063fcdf2bb8333764d97e319b7d0"}, + {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ab915a57c65f7a29353c8014ac4be685c8e4a19e792a79fe133a8e101111438e"}, + {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:35cda4e07f5e058a723436c4d2b7ba2124ab4e0aa49e6325aed5896507a8a42e"}, + {file = "aiohttp-3.11.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:af55314407714fe77a68a9ccaab90fdb5deb57342585fd4a3a8102b6d4370080"}, + {file = "aiohttp-3.11.13-cp311-cp311-win32.whl", hash = "sha256:42d689a5c0a0c357018993e471893e939f555e302313d5c61dfc566c2cad6185"}, + {file = "aiohttp-3.11.13-cp311-cp311-win_amd64.whl", hash = "sha256:b73a2b139782a07658fbf170fe4bcdf70fc597fae5ffe75e5b67674c27434a9f"}, + {file = "aiohttp-3.11.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eabb269dc3852537d57589b36d7f7362e57d1ece308842ef44d9830d2dc3c90"}, + {file = "aiohttp-3.11.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b77ee42addbb1c36d35aca55e8cc6d0958f8419e458bb70888d8c69a4ca833d"}, + {file = "aiohttp-3.11.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55789e93c5ed71832e7fac868167276beadf9877b85697020c46e9a75471f55f"}, + {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c929f9a7249a11e4aa5c157091cfad7f49cc6b13f4eecf9b747104befd9f56f2"}, + {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d33851d85537bbf0f6291ddc97926a754c8f041af759e0aa0230fe939168852b"}, + {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9229d8613bd8401182868fe95688f7581673e1c18ff78855671a4b8284f47bcb"}, + {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669dd33f028e54fe4c96576f406ebb242ba534dd3a981ce009961bf49960f117"}, + {file = "aiohttp-3.11.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1b20a1ace54af7db1f95af85da530fe97407d9063b7aaf9ce6a32f44730778"}, + {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5724cc77f4e648362ebbb49bdecb9e2b86d9b172c68a295263fa072e679ee69d"}, + {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:aa36c35e94ecdb478246dd60db12aba57cfcd0abcad43c927a8876f25734d496"}, + {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b5b37c863ad5b0892cc7a4ceb1e435e5e6acd3f2f8d3e11fa56f08d3c67b820"}, + {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e06cf4852ce8c4442a59bae5a3ea01162b8fcb49ab438d8548b8dc79375dad8a"}, + {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5194143927e494616e335d074e77a5dac7cd353a04755330c9adc984ac5a628e"}, + {file = "aiohttp-3.11.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afcb6b275c2d2ba5d8418bf30a9654fa978b4f819c2e8db6311b3525c86fe637"}, + {file = "aiohttp-3.11.13-cp312-cp312-win32.whl", hash = "sha256:7104d5b3943c6351d1ad7027d90bdd0ea002903e9f610735ac99df3b81f102ee"}, + {file = "aiohttp-3.11.13-cp312-cp312-win_amd64.whl", hash = "sha256:47dc018b1b220c48089b5b9382fbab94db35bef2fa192995be22cbad3c5730c8"}, + {file = "aiohttp-3.11.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9862d077b9ffa015dbe3ce6c081bdf35135948cb89116e26667dd183550833d1"}, + {file = "aiohttp-3.11.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbfef0666ae9e07abfa2c54c212ac18a1f63e13e0760a769f70b5717742f3ece"}, + {file = "aiohttp-3.11.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a1f7d857c4fcf7cabb1178058182c789b30d85de379e04f64c15b7e88d66fb"}, + {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba40b7ae0f81c7029583a338853f6607b6d83a341a3dcde8bed1ea58a3af1df9"}, + {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5b95787335c483cd5f29577f42bbe027a412c5431f2f80a749c80d040f7ca9f"}, + {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7d474c5c1f0b9405c1565fafdc4429fa7d986ccbec7ce55bc6a330f36409cad"}, + {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e83fb1991e9d8982b3b36aea1e7ad27ea0ce18c14d054c7a404d68b0319eebb"}, + {file = "aiohttp-3.11.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4586a68730bd2f2b04a83e83f79d271d8ed13763f64b75920f18a3a677b9a7f0"}, + {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fe4eb0e7f50cdb99b26250d9328faef30b1175a5dbcfd6d0578d18456bac567"}, + {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2a8a6bc19818ac3e5596310ace5aa50d918e1ebdcc204dc96e2f4d505d51740c"}, + {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f27eec42f6c3c1df09cfc1f6786308f8b525b8efaaf6d6bd76c1f52c6511f6a"}, + {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2a4a13dfbb23977a51853b419141cd0a9b9573ab8d3a1455c6e63561387b52ff"}, + {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:02876bf2f69b062584965507b07bc06903c2dc93c57a554b64e012d636952654"}, + {file = "aiohttp-3.11.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b992778d95b60a21c4d8d4a5f15aaab2bd3c3e16466a72d7f9bfd86e8cea0d4b"}, + {file = "aiohttp-3.11.13-cp313-cp313-win32.whl", hash = "sha256:507ab05d90586dacb4f26a001c3abf912eb719d05635cbfad930bdbeb469b36c"}, + {file = "aiohttp-3.11.13-cp313-cp313-win_amd64.whl", hash = "sha256:5ceb81a4db2decdfa087381b5fc5847aa448244f973e5da232610304e199e7b2"}, + {file = "aiohttp-3.11.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:51c3ff9c7a25f3cad5c09d9aacbc5aefb9267167c4652c1eb737989b554fe278"}, + {file = "aiohttp-3.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e271beb2b1dabec5cd84eb488bdabf9758d22ad13471e9c356be07ad139b3012"}, + {file = "aiohttp-3.11.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e9eb7e5764abcb49f0e2bd8f5731849b8728efbf26d0cac8e81384c95acec3f"}, + {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baae005092e3f200de02699314ac8933ec20abf998ec0be39448f6605bce93df"}, + {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1982c98ac62c132d2b773d50e2fcc941eb0b8bad3ec078ce7e7877c4d5a2dce7"}, + {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2b25b2eeb35707113b2d570cadc7c612a57f1c5d3e7bb2b13870fe284e08fc0"}, + {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b27961d65639128336b7a7c3f0046dcc62a9443d5ef962e3c84170ac620cec47"}, + {file = "aiohttp-3.11.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a01fe9f1e05025eacdd97590895e2737b9f851d0eb2e017ae9574d9a4f0b6252"}, + {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa1fb1b61881c8405829c50e9cc5c875bfdbf685edf57a76817dfb50643e4a1a"}, + {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:25de43bb3cf83ad83efc8295af7310219af6dbe4c543c2e74988d8e9c8a2a917"}, + {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe7065e2215e4bba63dc00db9ae654c1ba3950a5fff691475a32f511142fcddb"}, + {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7836587eef675a17d835ec3d98a8c9acdbeb2c1d72b0556f0edf4e855a25e9c1"}, + {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:85fa0b18558eb1427090912bd456a01f71edab0872f4e0f9e4285571941e4090"}, + {file = "aiohttp-3.11.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a86dc177eb4c286c19d1823ac296299f59ed8106c9536d2b559f65836e0fb2c6"}, + {file = "aiohttp-3.11.13-cp39-cp39-win32.whl", hash = "sha256:684eea71ab6e8ade86b9021bb62af4bf0881f6be4e926b6b5455de74e420783a"}, + {file = "aiohttp-3.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:82c249f2bfa5ecbe4a1a7902c81c0fba52ed9ebd0176ab3047395d02ad96cfcb"}, + {file = "aiohttp-3.11.13.tar.gz", hash = "sha256:8ce789231404ca8fff7f693cdce398abf6d90fd5dae2b1847477196c243b1fbb"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.2" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, + {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "asttokens" +version = "3.0.0" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, +] + +[package.extras] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "attrs" +version = "25.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, + {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "certifi" +version = "2025.1.31" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, +] + +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Windows\" or python_version >= \"3.12\" and platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "contourpy" +version = "1.3.1" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab"}, + {file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453"}, + {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277"}, + {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595"}, + {file = "contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697"}, + {file = "contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b"}, + {file = "contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85"}, + {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291"}, + {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f"}, + {file = "contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375"}, + {file = "contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509"}, + {file = "contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec"}, + {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b"}, + {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d"}, + {file = "contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e"}, + {file = "contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2"}, + {file = "contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7"}, + {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3"}, + {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1"}, + {file = "contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82"}, + {file = "contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30"}, + {file = "contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f"}, + {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242"}, + {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1"}, + {file = "contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750"}, + {file = "contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53"}, + {file = "contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "datasets" +version = "3.3.2" +description = "HuggingFace community-driven open-source library of datasets" +optional = false +python-versions = ">=3.9.0" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "datasets-3.3.2-py3-none-any.whl", hash = "sha256:fdaf3d5d70242621210b044e9b9b15a56e908bfc3e9d077bcf5605ac390f70bd"}, + {file = "datasets-3.3.2.tar.gz", hash = "sha256:20901a97da870fb80b407ccc45f034a7ac99accd07da897ed42f11641bdb8c6e"}, +] + +[package.dependencies] +aiohttp = "*" +dill = ">=0.3.0,<0.3.9" +filelock = "*" +fsspec = {version = ">=2023.1.0,<=2024.12.0", extras = ["http"]} +huggingface-hub = ">=0.24.0" +multiprocess = "<0.70.17" +numpy = ">=1.17" +packaging = "*" +pandas = "*" +pyarrow = ">=15.0.0" +pyyaml = ">=5.1" +requests = ">=2.32.2" +tqdm = ">=4.66.3" +xxhash = "*" + +[package.extras] +audio = ["librosa", "soundfile (>=0.12.1)", "soxr (>=0.4.0)"] +benchmarks = ["tensorflow (==2.12.0)", "torch (==2.0.1)", "transformers (==4.30.1)"] +dev = ["Pillow (>=9.4.0)", "absl-py", "decorator", "decord (==0.6.0)", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "ruff (>=0.3.0)", "s3fs", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tensorflow (>=2.16.0)", "tensorflow (>=2.6.0)", "tensorflow (>=2.6.0)", "tiktoken", "torch", "torch (>=2.0.0)", "torchdata", "transformers", "transformers (>=4.42.0)", "zstandard"] +docs = ["s3fs", "tensorflow (>=2.6.0)", "torch", "transformers"] +jax = ["jax (>=0.3.14)", "jaxlib (>=0.3.14)"] +quality = ["ruff (>=0.3.0)"] +s3 = ["s3fs"] +tensorflow = ["tensorflow (>=2.6.0)"] +tensorflow-gpu = ["tensorflow (>=2.6.0)"] +tests = ["Pillow (>=9.4.0)", "absl-py", "decorator", "decord (==0.6.0)", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tensorflow (>=2.16.0)", "tensorflow (>=2.6.0)", "tiktoken", "torch (>=2.0.0)", "torchdata", "transformers (>=4.42.0)", "zstandard"] +tests-numpy2 = ["Pillow (>=9.4.0)", "absl-py", "decorator", "decord (==0.6.0)", "elasticsearch (>=7.17.12,<8.0.0)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tiktoken", "torch (>=2.0.0)", "torchdata", "transformers (>=4.42.0)", "zstandard"] +torch = ["torch"] +vision = ["Pillow (>=9.4.0)"] + +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "filelock" +version = "3.17.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, + {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2)"] + +[[package]] +name = "fonttools" +version = "4.56.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "fonttools-4.56.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:331954d002dbf5e704c7f3756028e21db07097c19722569983ba4d74df014000"}, + {file = "fonttools-4.56.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d1613abd5af2f93c05867b3a3759a56e8bf97eb79b1da76b2bc10892f96ff16"}, + {file = "fonttools-4.56.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:705837eae384fe21cee5e5746fd4f4b2f06f87544fa60f60740007e0aa600311"}, + {file = "fonttools-4.56.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc871904a53a9d4d908673c6faa15689874af1c7c5ac403a8e12d967ebd0c0dc"}, + {file = "fonttools-4.56.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:38b947de71748bab150259ee05a775e8a0635891568e9fdb3cdd7d0e0004e62f"}, + {file = "fonttools-4.56.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:86b2a1013ef7a64d2e94606632683f07712045ed86d937c11ef4dde97319c086"}, + {file = "fonttools-4.56.0-cp310-cp310-win32.whl", hash = "sha256:133bedb9a5c6376ad43e6518b7e2cd2f866a05b1998f14842631d5feb36b5786"}, + {file = "fonttools-4.56.0-cp310-cp310-win_amd64.whl", hash = "sha256:17f39313b649037f6c800209984a11fc256a6137cbe5487091c6c7187cae4685"}, + {file = "fonttools-4.56.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ef04bc7827adb7532be3d14462390dd71287644516af3f1e67f1e6ff9c6d6df"}, + {file = "fonttools-4.56.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ffda9b8cd9cb8b301cae2602ec62375b59e2e2108a117746f12215145e3f786c"}, + {file = "fonttools-4.56.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e993e8db36306cc3f1734edc8ea67906c55f98683d6fd34c3fc5593fdbba4c"}, + {file = "fonttools-4.56.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:003548eadd674175510773f73fb2060bb46adb77c94854af3e0cc5bc70260049"}, + {file = "fonttools-4.56.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd9825822e7bb243f285013e653f6741954d8147427aaa0324a862cdbf4cbf62"}, + {file = "fonttools-4.56.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b23d30a2c0b992fb1c4f8ac9bfde44b5586d23457759b6cf9a787f1a35179ee0"}, + {file = "fonttools-4.56.0-cp311-cp311-win32.whl", hash = "sha256:47b5e4680002ae1756d3ae3b6114e20aaee6cc5c69d1e5911f5ffffd3ee46c6b"}, + {file = "fonttools-4.56.0-cp311-cp311-win_amd64.whl", hash = "sha256:14a3e3e6b211660db54ca1ef7006401e4a694e53ffd4553ab9bc87ead01d0f05"}, + {file = "fonttools-4.56.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6f195c14c01bd057bc9b4f70756b510e009c83c5ea67b25ced3e2c38e6ee6e9"}, + {file = "fonttools-4.56.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa760e5fe8b50cbc2d71884a1eff2ed2b95a005f02dda2fa431560db0ddd927f"}, + {file = "fonttools-4.56.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54a45d30251f1d729e69e5b675f9a08b7da413391a1227781e2a297fa37f6d2"}, + {file = "fonttools-4.56.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661a8995d11e6e4914a44ca7d52d1286e2d9b154f685a4d1f69add8418961563"}, + {file = "fonttools-4.56.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9d94449ad0a5f2a8bf5d2f8d71d65088aee48adbe45f3c5f8e00e3ad861ed81a"}, + {file = "fonttools-4.56.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f59746f7953f69cc3290ce2f971ab01056e55ddd0fb8b792c31a8acd7fee2d28"}, + {file = "fonttools-4.56.0-cp312-cp312-win32.whl", hash = "sha256:bce60f9a977c9d3d51de475af3f3581d9b36952e1f8fc19a1f2254f1dda7ce9c"}, + {file = "fonttools-4.56.0-cp312-cp312-win_amd64.whl", hash = "sha256:300c310bb725b2bdb4f5fc7e148e190bd69f01925c7ab437b9c0ca3e1c7cd9ba"}, + {file = "fonttools-4.56.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f20e2c0dfab82983a90f3d00703ac0960412036153e5023eed2b4641d7d5e692"}, + {file = "fonttools-4.56.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f36a0868f47b7566237640c026c65a86d09a3d9ca5df1cd039e30a1da73098a0"}, + {file = "fonttools-4.56.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62b4c6802fa28e14dba010e75190e0e6228513573f1eeae57b11aa1a39b7e5b1"}, + {file = "fonttools-4.56.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05d1f07eb0a7d755fbe01fee1fd255c3a4d3730130cf1bfefb682d18fd2fcea"}, + {file = "fonttools-4.56.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0073b62c3438cf0058488c002ea90489e8801d3a7af5ce5f7c05c105bee815c3"}, + {file = "fonttools-4.56.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cad98c94833465bcf28f51c248aaf07ca022efc6a3eba750ad9c1e0256d278"}, + {file = "fonttools-4.56.0-cp313-cp313-win32.whl", hash = "sha256:d0cb73ccf7f6d7ca8d0bc7ea8ac0a5b84969a41c56ac3ac3422a24df2680546f"}, + {file = "fonttools-4.56.0-cp313-cp313-win_amd64.whl", hash = "sha256:62cc1253827d1e500fde9dbe981219fea4eb000fd63402283472d38e7d8aa1c6"}, + {file = "fonttools-4.56.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3fd3fccb7b9adaaecfa79ad51b759f2123e1aba97f857936ce044d4f029abd71"}, + {file = "fonttools-4.56.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:193b86e9f769320bc98ffdb42accafb5d0c8c49bd62884f1c0702bc598b3f0a2"}, + {file = "fonttools-4.56.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e81c1cc80c1d8bf071356cc3e0e25071fbba1c75afc48d41b26048980b3c771"}, + {file = "fonttools-4.56.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9270505a19361e81eecdbc2c251ad1e1a9a9c2ad75fa022ccdee533f55535dc"}, + {file = "fonttools-4.56.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53f5e9767978a4daf46f28e09dbeb7d010319924ae622f7b56174b777258e5ba"}, + {file = "fonttools-4.56.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9da650cb29bc098b8cfd15ef09009c914b35c7986c8fa9f08b51108b7bc393b4"}, + {file = "fonttools-4.56.0-cp38-cp38-win32.whl", hash = "sha256:965d0209e6dbdb9416100123b6709cb13f5232e2d52d17ed37f9df0cc31e2b35"}, + {file = "fonttools-4.56.0-cp38-cp38-win_amd64.whl", hash = "sha256:654ac4583e2d7c62aebc6fc6a4c6736f078f50300e18aa105d87ce8925cfac31"}, + {file = "fonttools-4.56.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca7962e8e5fc047cc4e59389959843aafbf7445b6c08c20d883e60ced46370a5"}, + {file = "fonttools-4.56.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1af375734018951c31c0737d04a9d5fd0a353a0253db5fbed2ccd44eac62d8c"}, + {file = "fonttools-4.56.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:442ad4122468d0e47d83bc59d0e91b474593a8c813839e1872e47c7a0cb53b10"}, + {file = "fonttools-4.56.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf4f8d2a30b454ac682e12c61831dcb174950c406011418e739de592bbf8f76"}, + {file = "fonttools-4.56.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96a4271f63a615bcb902b9f56de00ea225d6896052c49f20d0c91e9f43529a29"}, + {file = "fonttools-4.56.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6c1d38642ca2dddc7ae992ef5d026e5061a84f10ff2b906be5680ab089f55bb8"}, + {file = "fonttools-4.56.0-cp39-cp39-win32.whl", hash = "sha256:2d351275f73ebdd81dd5b09a8b8dac7a30f29a279d41e1c1192aedf1b6dced40"}, + {file = "fonttools-4.56.0-cp39-cp39-win_amd64.whl", hash = "sha256:d6ca96d1b61a707ba01a43318c9c40aaf11a5a568d1e61146fafa6ab20890793"}, + {file = "fonttools-4.56.0-py3-none-any.whl", hash = "sha256:1088182f68c303b50ca4dc0c82d42083d176cba37af1937e1a976a31149d4d14"}, + {file = "fonttools-4.56.0.tar.gz", hash = "sha256:a114d1567e1a1586b7e9e7fc2ff686ca542a82769a296cef131e4c4af51e58f4"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "frozenlist" +version = "1.5.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, + {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, + {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, + {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, + {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, + {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, + {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, + {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, + {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, + {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, + {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, + {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, + {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, + {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, + {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, +] + +[[package]] +name = "fsspec" +version = "2024.12.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "fsspec-2024.12.0-py3-none-any.whl", hash = "sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2"}, + {file = "fsspec-2024.12.0.tar.gz", hash = "sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f"}, +] + +[package.dependencies] +aiohttp = {version = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1", optional = true, markers = "extra == \"http\""} + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +tqdm = ["tqdm"] + +[[package]] +name = "huggingface-hub" +version = "0.29.2" +description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "huggingface_hub-0.29.2-py3-none-any.whl", hash = "sha256:c56f20fca09ef19da84dcde2b76379ecdaddf390b083f59f166715584953307d"}, + {file = "huggingface_hub-0.29.2.tar.gz", hash = "sha256:590b29c0dcbd0ee4b7b023714dc1ad8563fe4a68a91463438b74e980d28afaf3"}, +] + +[package.dependencies] +filelock = "*" +fsspec = ">=2023.5.0" +packaging = ">=20.9" +pyyaml = ">=5.1" +requests = "*" +tqdm = ">=4.42.1" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +cli = ["InquirerPy (==0.3.4)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] +hf-transfer = ["hf-transfer (>=0.1.4)"] +inference = ["aiohttp"] +quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.9.0)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +tensorflow-testing = ["keras (<3.0)", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +torch = ["safetensors[torch]", "torch"] +typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "joblib" +version = "1.4.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, + {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"}, + {file = "kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed"}, + {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605"}, + {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751"}, + {file = "kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561"}, + {file = "kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6"}, + {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc"}, + {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34"}, + {file = "kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31"}, + {file = "kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d"}, + {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50"}, + {file = "kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1"}, + {file = "kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc"}, + {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957"}, + {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2"}, + {file = "kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90"}, + {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b"}, + {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b"}, + {file = "kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e"}, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "matplotlib" +version = "3.10.1" +description = "Python plotting package" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "matplotlib-3.10.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ff2ae14910be903f4a24afdbb6d7d3a6c44da210fc7d42790b87aeac92238a16"}, + {file = "matplotlib-3.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0721a3fd3d5756ed593220a8b86808a36c5031fce489adb5b31ee6dbb47dd5b2"}, + {file = "matplotlib-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0673b4b8f131890eb3a1ad058d6e065fb3c6e71f160089b65f8515373394698"}, + {file = "matplotlib-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e875b95ac59a7908978fe307ecdbdd9a26af7fa0f33f474a27fcf8c99f64a19"}, + {file = "matplotlib-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2589659ea30726284c6c91037216f64a506a9822f8e50592d48ac16a2f29e044"}, + {file = "matplotlib-3.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a97ff127f295817bc34517255c9db6e71de8eddaab7f837b7d341dee9f2f587f"}, + {file = "matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401"}, + {file = "matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe"}, + {file = "matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd"}, + {file = "matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c"}, + {file = "matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7"}, + {file = "matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a"}, + {file = "matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107"}, + {file = "matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be"}, + {file = "matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6"}, + {file = "matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d"}, + {file = "matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea"}, + {file = "matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c"}, + {file = "matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b"}, + {file = "matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1"}, + {file = "matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3"}, + {file = "matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6"}, + {file = "matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b"}, + {file = "matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473"}, + {file = "matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01"}, + {file = "matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb"}, + {file = "matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972"}, + {file = "matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3"}, + {file = "matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f"}, + {file = "matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9"}, + {file = "matplotlib-3.10.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:648406f1899f9a818cef8c0231b44dcfc4ff36f167101c3fd1c9151f24220fdc"}, + {file = "matplotlib-3.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:02582304e352f40520727984a5a18f37e8187861f954fea9be7ef06569cf85b4"}, + {file = "matplotlib-3.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3809916157ba871bcdd33d3493acd7fe3037db5daa917ca6e77975a94cef779"}, + {file = "matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4)"] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "multidict" +version = "6.1.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, +] + +[[package]] +name = "multiprocess" +version = "0.70.16" +description = "better multiprocessing and multithreading in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee"}, + {file = "multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec"}, + {file = "multiprocess-0.70.16-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37b55f71c07e2d741374998c043b9520b626a8dddc8b3129222ca4f1a06ef67a"}, + {file = "multiprocess-0.70.16-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba8c31889abf4511c7308a8c52bb4a30b9d590e7f58523302ba00237702ca054"}, + {file = "multiprocess-0.70.16-pp39-pypy39_pp73-macosx_10_13_x86_64.whl", hash = "sha256:0dfd078c306e08d46d7a8d06fb120313d87aa43af60d66da43ffff40b44d2f41"}, + {file = "multiprocess-0.70.16-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e7b9d0f307cd9bd50851afaac0dba2cb6c44449efff697df7c7645f7d3f2be3a"}, + {file = "multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02"}, + {file = "multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a"}, + {file = "multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e"}, + {file = "multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435"}, + {file = "multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3"}, + {file = "multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1"}, +] + +[package.dependencies] +dill = ">=0.3.8" + +[[package]] +name = "networkx" +version = "3.4.2" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, + {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, +] + +[package.extras] +default = ["matplotlib (>=3.7)", "numpy (>=1.24)", "pandas (>=2.0)", "scipy (>=1.10,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.15)", "sphinx (>=7.3)", "sphinx-gallery (>=0.16)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=1.9)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "numpy" +version = "2.2.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71"}, + {file = "numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787"}, + {file = "numpy-2.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716"}, + {file = "numpy-2.2.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b"}, + {file = "numpy-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3"}, + {file = "numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52"}, + {file = "numpy-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b"}, + {file = "numpy-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027"}, + {file = "numpy-2.2.3-cp310-cp310-win32.whl", hash = "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094"}, + {file = "numpy-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb"}, + {file = "numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8"}, + {file = "numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b"}, + {file = "numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a"}, + {file = "numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636"}, + {file = "numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d"}, + {file = "numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb"}, + {file = "numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2"}, + {file = "numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b"}, + {file = "numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5"}, + {file = "numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f"}, + {file = "numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d"}, + {file = "numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95"}, + {file = "numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea"}, + {file = "numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532"}, + {file = "numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e"}, + {file = "numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe"}, + {file = "numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021"}, + {file = "numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8"}, + {file = "numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe"}, + {file = "numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d"}, + {file = "numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba"}, + {file = "numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50"}, + {file = "numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1"}, + {file = "numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5"}, + {file = "numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2"}, + {file = "numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1"}, + {file = "numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304"}, + {file = "numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d"}, + {file = "numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693"}, + {file = "numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b"}, + {file = "numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890"}, + {file = "numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c"}, + {file = "numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94"}, + {file = "numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0"}, + {file = "numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610"}, + {file = "numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76"}, + {file = "numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a"}, + {file = "numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf"}, + {file = "numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef"}, + {file = "numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082"}, + {file = "numpy-2.2.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d"}, + {file = "numpy-2.2.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9"}, + {file = "numpy-2.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e"}, + {file = "numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4"}, + {file = "numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020"}, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.4.5.8" +description = "CUBLAS native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3"}, + {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b"}, + {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-win_amd64.whl", hash = "sha256:5a796786da89203a0657eda402bcdcec6180254a8ac22d72213abc42069522dc"}, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.4.127" +description = "CUDA profiling tools runtime libs." +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a"}, + {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb"}, + {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:5688d203301ab051449a2b1cb6690fbe90d2b372f411521c86018b950f3d7922"}, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.4.127" +description = "NVRTC native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198"}, + {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338"}, + {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:a961b2f1d5f17b14867c619ceb99ef6fcec12e46612711bcec78eb05068a60ec"}, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.4.127" +description = "CUDA Runtime native Libraries" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3"}, + {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5"}, + {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:09c2e35f48359752dfa822c09918211844a3d93c100a715d79b59591130c5e1e"}, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.1.0.70" +description = "cuDNN runtime libraries" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f"}, + {file = "nvidia_cudnn_cu12-9.1.0.70-py3-none-win_amd64.whl", hash = "sha256:6278562929433d68365a07a4a1546c237ba2849852c0d4b2262a486e805b977a"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.2.1.3" +description = "CUFFT native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399"}, + {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9"}, + {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-win_amd64.whl", hash = "sha256:d802f4954291101186078ccbe22fc285a902136f974d369540fd4a5333d1440b"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.5.147" +description = "CURAND native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9"}, + {file = "nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b"}, + {file = "nvidia_curand_cu12-10.3.5.147-py3-none-win_amd64.whl", hash = "sha256:f307cc191f96efe9e8f05a87096abc20d08845a841889ef78cb06924437f6771"}, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.6.1.9" +description = "CUDA solver native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e"}, + {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260"}, + {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-win_amd64.whl", hash = "sha256:e77314c9d7b694fcebc84f58989f3aa4fb4cb442f12ca1a9bde50f5e8f6d1b9c"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" +nvidia-cusparse-cu12 = "*" +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.3.1.170" +description = "CUSPARSE native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3"}, + {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1"}, + {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-win_amd64.whl", hash = "sha256:9bc90fb087bc7b4c15641521f31c0371e9a612fc2ba12c338d3ae032e6b6797f"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.6.2" +description = "NVIDIA cuSPARSELt" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:067a7f6d03ea0d4841c85f0c6f1991c5dda98211f6302cb83a4ab234ee95bef8"}, + {file = "nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:df2c24502fd76ebafe7457dbc4716b2fec071aabaed4fb7691a201cde03704d9"}, + {file = "nvidia_cusparselt_cu12-0.6.2-py3-none-win_amd64.whl", hash = "sha256:0057c91d230703924c0422feabe4ce768841f9b4b44d28586b6f6d2eb86fbe70"}, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.21.5" +description = "NVIDIA Collective Communication Library (NCCL) Runtime" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0"}, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.4.127" +description = "Nvidia JIT LTO Library" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83"}, + {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57"}, + {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:fd9020c501d27d135f983c6d3e244b197a7ccad769e34df53a42e276b0e25fa1"}, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.4.127" +description = "NVIDIA Tools Extension" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3"}, + {file = "nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a"}, + {file = "nvidia_nvtx_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:641dccaaa1139f3ffb0d3164b4b84f9d253397e38246a4f2f36728b48566d485"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pandas" +version = "2.2.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "pillow" +version = "11.1.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"}, + {file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"}, + {file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"}, + {file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"}, + {file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"}, + {file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"}, + {file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"}, + {file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"}, + {file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"}, + {file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"}, + {file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"}, + {file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"}, + {file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"}, + {file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"}, + {file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"}, + {file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"}, + {file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"}, + {file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"}, + {file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"}, + {file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"}, + {file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + +[[package]] +name = "propcache" +version = "0.3.0" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d"}, + {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c"}, + {file = "propcache-0.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e7420211f5a65a54675fd860ea04173cde60a7cc20ccfbafcccd155225f8bc"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3302c5287e504d23bb0e64d2a921d1eb4a03fb93a0a0aa3b53de059f5a5d737d"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e2e068a83552ddf7a39a99488bcba05ac13454fb205c847674da0352602082f"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d913d36bdaf368637b4f88d554fb9cb9d53d6920b9c5563846555938d5450bf"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ee1983728964d6070ab443399c476de93d5d741f71e8f6e7880a065f878e0b9"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36ca5e9a21822cc1746023e88f5c0af6fce3af3b85d4520efb1ce4221bed75cc"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9ecde3671e62eeb99e977f5221abcf40c208f69b5eb986b061ccec317c82ebd0"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d383bf5e045d7f9d239b38e6acadd7b7fdf6c0087259a84ae3475d18e9a2ae8b"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8cb625bcb5add899cb8ba7bf716ec1d3e8f7cdea9b0713fa99eadf73b6d4986f"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5fa159dcee5dba00c1def3231c249cf261185189205073bde13797e57dd7540a"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7080b0159ce05f179cfac592cda1a82898ca9cd097dacf8ea20ae33474fbb25"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed7161bccab7696a473fe7ddb619c1d75963732b37da4618ba12e60899fefe4f"}, + {file = "propcache-0.3.0-cp310-cp310-win32.whl", hash = "sha256:bf0d9a171908f32d54f651648c7290397b8792f4303821c42a74e7805bfb813c"}, + {file = "propcache-0.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:42924dc0c9d73e49908e35bbdec87adedd651ea24c53c29cac103ede0ea1d340"}, + {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9ddd49258610499aab83b4f5b61b32e11fce873586282a0e972e5ab3bcadee51"}, + {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2578541776769b500bada3f8a4eeaf944530516b6e90c089aa368266ed70c49e"}, + {file = "propcache-0.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8074c5dd61c8a3e915fa8fc04754fa55cfa5978200d2daa1e2d4294c1f136aa"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b58229a844931bca61b3a20efd2be2a2acb4ad1622fc026504309a6883686fbf"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45377d5d6fefe1677da2a2c07b024a6dac782088e37c0b1efea4cfe2b1be19b"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5060592d83454e8063e487696ac3783cc48c9a329498bafae0d972bc7816c9"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15010f29fbed80e711db272909a074dc79858c6d28e2915704cfc487a8ac89c6"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a254537b9b696ede293bfdbc0a65200e8e4507bc9f37831e2a0318a9b333c85c"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2b975528998de037dfbc10144b8aed9b8dd5a99ec547f14d1cb7c5665a43f075"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:19d36bb351ad5554ff20f2ae75f88ce205b0748c38b146c75628577020351e3c"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6032231d4a5abd67c7f71168fd64a47b6b451fbcb91c8397c2f7610e67683810"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6985a593417cdbc94c7f9c3403747335e450c1599da1647a5af76539672464d3"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a1948df1bb1d56b5e7b0553c0fa04fd0e320997ae99689488201f19fa90d2e7"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8319293e85feadbbfe2150a5659dbc2ebc4afdeaf7d98936fb9a2f2ba0d4c35c"}, + {file = "propcache-0.3.0-cp311-cp311-win32.whl", hash = "sha256:63f26258a163c34542c24808f03d734b338da66ba91f410a703e505c8485791d"}, + {file = "propcache-0.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cacea77ef7a2195f04f9279297684955e3d1ae4241092ff0cfcef532bb7a1c32"}, + {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e"}, + {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af"}, + {file = "propcache-0.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c"}, + {file = "propcache-0.3.0-cp312-cp312-win32.whl", hash = "sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d"}, + {file = "propcache-0.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57"}, + {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568"}, + {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9"}, + {file = "propcache-0.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626"}, + {file = "propcache-0.3.0-cp313-cp313-win32.whl", hash = "sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374"}, + {file = "propcache-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a"}, + {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf"}, + {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0"}, + {file = "propcache-0.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf"}, + {file = "propcache-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863"}, + {file = "propcache-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46"}, + {file = "propcache-0.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:03c091bb752349402f23ee43bb2bff6bd80ccab7c9df6b88ad4322258d6960fc"}, + {file = "propcache-0.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46ed02532cb66612d42ae5c3929b5e98ae330ea0f3900bc66ec5f4862069519b"}, + {file = "propcache-0.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11ae6a8a01b8a4dc79093b5d3ca2c8a4436f5ee251a9840d7790dccbd96cb649"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df03cd88f95b1b99052b52b1bb92173229d7a674df0ab06d2b25765ee8404bce"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03acd9ff19021bd0567582ac88f821b66883e158274183b9e5586f678984f8fe"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd54895e4ae7d32f1e3dd91261df46ee7483a735017dc6f987904f194aa5fd14"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a67e5c04e3119594d8cfae517f4b9330c395df07ea65eab16f3d559b7068fe"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee25f1ac091def37c4b59d192bbe3a206298feeb89132a470325bf76ad122a1e"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58e6d2a5a7cb3e5f166fd58e71e9a4ff504be9dc61b88167e75f835da5764d07"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:be90c94570840939fecedf99fa72839aed70b0ced449b415c85e01ae67422c90"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49ea05212a529c2caffe411e25a59308b07d6e10bf2505d77da72891f9a05641"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:119e244ab40f70a98c91906d4c1f4c5f2e68bd0b14e7ab0a06922038fae8a20f"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:507c5357a8d8b4593b97fb669c50598f4e6cccbbf77e22fa9598aba78292b4d7"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8526b0941ec5a40220fc4dfde76aed58808e2b309c03e9fa8e2260083ef7157f"}, + {file = "propcache-0.3.0-cp39-cp39-win32.whl", hash = "sha256:7cedd25e5f678f7738da38037435b340694ab34d424938041aa630d8bac42663"}, + {file = "propcache-0.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf4298f366ca7e1ad1d21bbb58300a6985015909964077afd37559084590c929"}, + {file = "propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043"}, + {file = "propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5"}, +] + +[[package]] +name = "psutil" +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, +] + +[package.extras] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + +[[package]] +name = "pyarrow" +version = "19.0.1" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fc28912a2dc924dddc2087679cc8b7263accc71b9ff025a1362b004711661a69"}, + {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fca15aabbe9b8355800d923cc2e82c8ef514af321e18b437c3d782aa884eaeec"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad76aef7f5f7e4a757fddcdcf010a8290958f09e3470ea458c80d26f4316ae89"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d03c9d6f2a3dffbd62671ca070f13fc527bb1867b4ec2b98c7eeed381d4f389a"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:65cf9feebab489b19cdfcfe4aa82f62147218558d8d3f0fc1e9dea0ab8e7905a"}, + {file = "pyarrow-19.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:41f9706fbe505e0abc10e84bf3a906a1338905cbbcf1177b71486b03e6ea6608"}, + {file = "pyarrow-19.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6cb2335a411b713fdf1e82a752162f72d4a7b5dbc588e32aa18383318b05866"}, + {file = "pyarrow-19.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc55d71898ea30dc95900297d191377caba257612f384207fe9f8293b5850f90"}, + {file = "pyarrow-19.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:7a544ec12de66769612b2d6988c36adc96fb9767ecc8ee0a4d270b10b1c51e00"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0148bb4fc158bfbc3d6dfe5001d93ebeed253793fff4435167f6ce1dc4bddeae"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f24faab6ed18f216a37870d8c5623f9c044566d75ec586ef884e13a02a9d62c5"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4982f8e2b7afd6dae8608d70ba5bd91699077323f812a0448d8b7abdff6cb5d3"}, + {file = "pyarrow-19.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:49a3aecb62c1be1d822f8bf629226d4a96418228a42f5b40835c1f10d42e4db6"}, + {file = "pyarrow-19.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:008a4009efdb4ea3d2e18f05cd31f9d43c388aad29c636112c2966605ba33466"}, + {file = "pyarrow-19.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:80b2ad2b193e7d19e81008a96e313fbd53157945c7be9ac65f44f8937a55427b"}, + {file = "pyarrow-19.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:ee8dec072569f43835932a3b10c55973593abc00936c202707a4ad06af7cb294"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d5d1ec7ec5324b98887bdc006f4d2ce534e10e60f7ad995e7875ffa0ff9cb14"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ad4c0eb4e2a9aeb990af6c09e6fa0b195c8c0e7b272ecc8d4d2b6574809d34"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d383591f3dcbe545f6cc62daaef9c7cdfe0dff0fb9e1c8121101cabe9098cfa6"}, + {file = "pyarrow-19.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b4c4156a625f1e35d6c0b2132635a237708944eb41df5fbe7d50f20d20c17832"}, + {file = "pyarrow-19.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:5bd1618ae5e5476b7654c7b55a6364ae87686d4724538c24185bbb2952679960"}, + {file = "pyarrow-19.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e45274b20e524ae5c39d7fc1ca2aa923aab494776d2d4b316b49ec7572ca324c"}, + {file = "pyarrow-19.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d9dedeaf19097a143ed6da37f04f4051aba353c95ef507764d344229b2b740ae"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ebfb5171bb5f4a52319344ebbbecc731af3f021e49318c74f33d520d31ae0c4"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a21d39fbdb948857f67eacb5bbaaf36802de044ec36fbef7a1c8f0dd3a4ab2"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:99bc1bec6d234359743b01e70d4310d0ab240c3d6b0da7e2a93663b0158616f6"}, + {file = "pyarrow-19.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1b93ef2c93e77c442c979b0d596af45e4665d8b96da598db145b0fec014b9136"}, + {file = "pyarrow-19.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9d46e06846a41ba906ab25302cf0fd522f81aa2a85a71021826f34639ad31ef"}, + {file = "pyarrow-19.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c0fe3dbbf054a00d1f162fda94ce236a899ca01123a798c561ba307ca38af5f0"}, + {file = "pyarrow-19.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:96606c3ba57944d128e8a8399da4812f56c7f61de8c647e3470b417f795d0ef9"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f04d49a6b64cf24719c080b3c2029a3a5b16417fd5fd7c4041f94233af732f3"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a9137cf7e1640dce4c190551ee69d478f7121b5c6f323553b319cac936395f6"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7c1bca1897c28013db5e4c83944a2ab53231f541b9e0c3f4791206d0c0de389a"}, + {file = "pyarrow-19.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:58d9397b2e273ef76264b45531e9d552d8ec8a6688b7390b5be44c02a37aade8"}, + {file = "pyarrow-19.0.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:b9766a47a9cb56fefe95cb27f535038b5a195707a08bf61b180e642324963b46"}, + {file = "pyarrow-19.0.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:6c5941c1aac89a6c2f2b16cd64fe76bcdb94b2b1e99ca6459de4e6f07638d755"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd44d66093a239358d07c42a91eebf5015aa54fccba959db899f932218ac9cc8"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:335d170e050bcc7da867a1ed8ffb8b44c57aaa6e0843b156a501298657b1e972"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:1c7556165bd38cf0cd992df2636f8bcdd2d4b26916c6b7e646101aff3c16f76f"}, + {file = "pyarrow-19.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:699799f9c80bebcf1da0983ba86d7f289c5a2a5c04b945e2f2bcf7e874a91911"}, + {file = "pyarrow-19.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8464c9fbe6d94a7fe1599e7e8965f350fd233532868232ab2596a71586c5a429"}, + {file = "pyarrow-19.0.1.tar.gz", hash = "sha256:3bf266b485df66a400f282ac0b6d1b500b9d2ae73314a153dbe97d6d5cc8a99e"}, +] + +[package.extras] +test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] + +[[package]] +name = "pydot" +version = "3.0.4" +description = "Python interface to Graphviz's Dot" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pydot-3.0.4-py3-none-any.whl", hash = "sha256:bfa9c3fc0c44ba1d132adce131802d7df00429d1a79cc0346b0a5cd374dbe9c6"}, + {file = "pydot-3.0.4.tar.gz", hash = "sha256:3ce88b2558f3808b0376f22bfa6c263909e1c3981e2a7b629b65b451eee4a25d"}, +] + +[package.dependencies] +pyparsing = ">=3.0.9" + +[package.extras] +dev = ["chardet", "parameterized", "ruff"] +release = ["zest.releaser[recommended]"] +tests = ["chardet", "parameterized", "pytest", "pytest-cov", "pytest-xdist[psutil]", "ruff", "tox"] + +[[package]] +name = "pygments" +version = "2.19.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyparsing" +version = "3.2.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, + {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2025.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "regex" +version = "2024.11.6" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.9.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "safetensors" +version = "0.5.3" +description = "" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073"}, + {file = "safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d"}, + {file = "safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b"}, + {file = "safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff"}, + {file = "safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135"}, + {file = "safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04"}, + {file = "safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace"}, + {file = "safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11"}, + {file = "safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965"}, +] + +[package.extras] +all = ["safetensors[jax]", "safetensors[numpy]", "safetensors[paddlepaddle]", "safetensors[pinned-tf]", "safetensors[quality]", "safetensors[testing]", "safetensors[torch]"] +dev = ["safetensors[all]"] +jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "safetensors[numpy]"] +mlx = ["mlx (>=0.0.9)"] +numpy = ["numpy (>=1.21.6)"] +paddlepaddle = ["paddlepaddle (>=2.4.1)", "safetensors[numpy]"] +pinned-tf = ["safetensors[numpy]", "tensorflow (==2.18.0)"] +quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] +tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] +testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools-rust (>=1.5.2)"] +torch = ["safetensors[numpy]", "torch (>=1.10)"] + +[[package]] +name = "scikit-learn" +version = "1.6.1" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "scikit_learn-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e"}, + {file = "scikit_learn-1.6.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36"}, + {file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8634c4bd21a2a813e0a7e3900464e6d593162a29dd35d25bdf0103b3fce60ed5"}, + {file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775da975a471c4f6f467725dff0ced5c7ac7bda5e9316b260225b48475279a1b"}, + {file = "scikit_learn-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:8a600c31592bd7dab31e1c61b9bbd6dea1b3433e67d264d17ce1017dbdce8002"}, + {file = "scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33"}, + {file = "scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d"}, + {file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2"}, + {file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8"}, + {file = "scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415"}, + {file = "scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b"}, + {file = "scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2"}, + {file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f"}, + {file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86"}, + {file = "scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52"}, + {file = "scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322"}, + {file = "scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1"}, + {file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348"}, + {file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97"}, + {file = "scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f"}, + {file = "scikit_learn-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6849dd3234e87f55dce1db34c89a810b489ead832aaf4d4550b7ea85628be6c1"}, + {file = "scikit_learn-1.6.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e7be3fa5d2eb9be7d77c3734ff1d599151bb523674be9b834e8da6abe132f44e"}, + {file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44a17798172df1d3c1065e8fcf9019183f06c87609b49a124ebdf57ae6cb0107"}, + {file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b7a3b86e411e4bce21186e1c180d792f3d99223dcfa3b4f597ecc92fa1a422"}, + {file = "scikit_learn-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7a73d457070e3318e32bdb3aa79a8d990474f19035464dfd8bede2883ab5dc3b"}, + {file = "scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e"}, +] + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.19.5" +scipy = ">=1.6.0" +threadpoolctl = ">=3.1.0" + +[package.extras] +benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==2.5.6)"] +tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.5.1)", "scikit-image (>=0.17.2)"] + +[[package]] +name = "scipy" +version = "1.15.2" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "scipy-1.15.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a2ec871edaa863e8213ea5df811cd600734f6400b4af272e1c011e69401218e9"}, + {file = "scipy-1.15.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6f223753c6ea76983af380787611ae1291e3ceb23917393079dcc746ba60cfb5"}, + {file = "scipy-1.15.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ecf797d2d798cf7c838c6d98321061eb3e72a74710e6c40540f0e8087e3b499e"}, + {file = "scipy-1.15.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:9b18aa747da280664642997e65aab1dd19d0c3d17068a04b3fe34e2559196cb9"}, + {file = "scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3"}, + {file = "scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d"}, + {file = "scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58"}, + {file = "scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa"}, + {file = "scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65"}, + {file = "scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4"}, + {file = "scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1"}, + {file = "scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971"}, + {file = "scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655"}, + {file = "scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e"}, + {file = "scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0"}, + {file = "scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40"}, + {file = "scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462"}, + {file = "scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737"}, + {file = "scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd"}, + {file = "scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301"}, + {file = "scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93"}, + {file = "scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20"}, + {file = "scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e"}, + {file = "scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8"}, + {file = "scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11"}, + {file = "scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53"}, + {file = "scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded"}, + {file = "scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf"}, + {file = "scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37"}, + {file = "scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d"}, + {file = "scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb"}, + {file = "scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27"}, + {file = "scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0"}, + {file = "scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32"}, + {file = "scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d"}, + {file = "scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f"}, + {file = "scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9"}, + {file = "scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f"}, + {file = "scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6"}, + {file = "scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af"}, + {file = "scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274"}, + {file = "scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776"}, + {file = "scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828"}, + {file = "scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28"}, + {file = "scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db"}, + {file = "scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.5" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.16.5)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "seqeval" +version = "1.2.2" +description = "Testing framework for sequence labeling" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "seqeval-1.2.2.tar.gz", hash = "sha256:f28e97c3ab96d6fcd32b648f6438ff2e09cfba87f05939da9b3970713ec56e6f"}, +] + +[package.dependencies] +numpy = ">=1.14.0" +scikit-learn = ">=0.21.3" + +[[package]] +name = "setuptools" +version = "75.8.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "setuptools-75.8.2-py3-none-any.whl", hash = "sha256:558e47c15f1811c1fa7adbd0096669bf76c1d3f433f58324df69f3f5ecac4e8f"}, + {file = "setuptools-75.8.2.tar.gz", hash = "sha256:4880473a969e5f23f2a2be3646b2dfd84af9028716d398e46192f84bc36900d2"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "sympy" +version = "1.13.1" +description = "Computer algebra system (CAS) in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, + {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, +] + +[package.dependencies] +mpmath = ">=1.1.0,<1.4" + +[package.extras] +dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] + +[[package]] +name = "threadpoolctl" +version = "3.5.0" +description = "threadpoolctl" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, + {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, +] + +[[package]] +name = "tokenizers" +version = "0.20.3" +description = "" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "tokenizers-0.20.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:31ccab28dbb1a9fe539787210b0026e22debeab1662970f61c2d921f7557f7e4"}, + {file = "tokenizers-0.20.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6361191f762bda98c773da418cf511cbaa0cb8d0a1196f16f8c0119bde68ff8"}, + {file = "tokenizers-0.20.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f128d5da1202b78fa0a10d8d938610472487da01b57098d48f7e944384362514"}, + {file = "tokenizers-0.20.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79c4121a2e9433ad7ef0769b9ca1f7dd7fa4c0cd501763d0a030afcbc6384481"}, + {file = "tokenizers-0.20.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7850fde24197fe5cd6556e2fdba53a6d3bae67c531ea33a3d7c420b90904141"}, + {file = "tokenizers-0.20.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b357970c095dc134978a68c67d845a1e3803ab7c4fbb39195bde914e7e13cf8b"}, + {file = "tokenizers-0.20.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a333d878c4970b72d6c07848b90c05f6b045cf9273fc2bc04a27211721ad6118"}, + {file = "tokenizers-0.20.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd9fee817f655a8f50049f685e224828abfadd436b8ff67979fc1d054b435f1"}, + {file = "tokenizers-0.20.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9e7816808b402129393a435ea2a509679b41246175d6e5e9f25b8692bfaa272b"}, + {file = "tokenizers-0.20.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba96367db9d8a730d3a1d5996b4b7babb846c3994b8ef14008cd8660f55db59d"}, + {file = "tokenizers-0.20.3-cp310-none-win32.whl", hash = "sha256:ee31ba9d7df6a98619426283e80c6359f167e2e9882d9ce1b0254937dbd32f3f"}, + {file = "tokenizers-0.20.3-cp310-none-win_amd64.whl", hash = "sha256:a845c08fdad554fe0871d1255df85772f91236e5fd6b9287ef8b64f5807dbd0c"}, + {file = "tokenizers-0.20.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:585b51e06ca1f4839ce7759941e66766d7b060dccfdc57c4ca1e5b9a33013a90"}, + {file = "tokenizers-0.20.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61cbf11954f3b481d08723ebd048ba4b11e582986f9be74d2c3bdd9293a4538d"}, + {file = "tokenizers-0.20.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef820880d5e4e8484e2fa54ff8d297bb32519eaa7815694dc835ace9130a3eea"}, + {file = "tokenizers-0.20.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:67ef4dcb8841a4988cd00dd288fb95dfc8e22ed021f01f37348fd51c2b055ba9"}, + {file = "tokenizers-0.20.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff1ef8bd47a02b0dc191688ccb4da53600df5d4c9a05a4b68e1e3de4823e78eb"}, + {file = "tokenizers-0.20.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:444d188186eab3148baf0615b522461b41b1f0cd58cd57b862ec94b6ac9780f1"}, + {file = "tokenizers-0.20.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37c04c032c1442740b2c2d925f1857885c07619224a533123ac7ea71ca5713da"}, + {file = "tokenizers-0.20.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:453c7769d22231960ee0e883d1005c93c68015025a5e4ae56275406d94a3c907"}, + {file = "tokenizers-0.20.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4bb31f7b2847e439766aaa9cc7bccf7ac7088052deccdb2275c952d96f691c6a"}, + {file = "tokenizers-0.20.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:843729bf0f991b29655a069a2ff58a4c24375a553c70955e15e37a90dd4e045c"}, + {file = "tokenizers-0.20.3-cp311-none-win32.whl", hash = "sha256:efcce3a927b1e20ca694ba13f7a68c59b0bd859ef71e441db68ee42cf20c2442"}, + {file = "tokenizers-0.20.3-cp311-none-win_amd64.whl", hash = "sha256:88301aa0801f225725b6df5dea3d77c80365ff2362ca7e252583f2b4809c4cc0"}, + {file = "tokenizers-0.20.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:49d12a32e190fad0e79e5bdb788d05da2f20d8e006b13a70859ac47fecf6ab2f"}, + {file = "tokenizers-0.20.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:282848cacfb9c06d5e51489f38ec5aa0b3cd1e247a023061945f71f41d949d73"}, + {file = "tokenizers-0.20.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abe4e08c7d0cd6154c795deb5bf81d2122f36daf075e0c12a8b050d824ef0a64"}, + {file = "tokenizers-0.20.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca94fc1b73b3883c98f0c88c77700b13d55b49f1071dfd57df2b06f3ff7afd64"}, + {file = "tokenizers-0.20.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef279c7e239f95c8bdd6ff319d9870f30f0d24915b04895f55b1adcf96d6c60d"}, + {file = "tokenizers-0.20.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16384073973f6ccbde9852157a4fdfe632bb65208139c9d0c0bd0176a71fd67f"}, + {file = "tokenizers-0.20.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:312d522caeb8a1a42ebdec87118d99b22667782b67898a76c963c058a7e41d4f"}, + {file = "tokenizers-0.20.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2b7cb962564785a83dafbba0144ecb7f579f1d57d8c406cdaa7f32fe32f18ad"}, + {file = "tokenizers-0.20.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:124c5882ebb88dadae1fc788a582299fcd3a8bd84fc3e260b9918cf28b8751f5"}, + {file = "tokenizers-0.20.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2b6e54e71f84c4202111a489879005cb14b92616a87417f6c102c833af961ea2"}, + {file = "tokenizers-0.20.3-cp312-none-win32.whl", hash = "sha256:83d9bfbe9af86f2d9df4833c22e94d94750f1d0cd9bfb22a7bb90a86f61cdb1c"}, + {file = "tokenizers-0.20.3-cp312-none-win_amd64.whl", hash = "sha256:44def74cee574d609a36e17c8914311d1b5dbcfe37c55fd29369d42591b91cf2"}, + {file = "tokenizers-0.20.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0b630e0b536ef0e3c8b42c685c1bc93bd19e98c0f1543db52911f8ede42cf84"}, + {file = "tokenizers-0.20.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a02d160d2b19bcbfdf28bd9a4bf11be4cb97d0499c000d95d4c4b1a4312740b6"}, + {file = "tokenizers-0.20.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e3d80d89b068bc30034034b5319218c7c0a91b00af19679833f55f3becb6945"}, + {file = "tokenizers-0.20.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:174a54910bed1b089226512b4458ea60d6d6fd93060254734d3bc3540953c51c"}, + {file = "tokenizers-0.20.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:098b8a632b8656aa5802c46689462c5c48f02510f24029d71c208ec2c822e771"}, + {file = "tokenizers-0.20.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78c8c143e3ae41e718588281eb3e212c2b31623c9d6d40410ec464d7d6221fb5"}, + {file = "tokenizers-0.20.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b26b0aadb18cd8701077362ba359a06683662d5cafe3e8e8aba10eb05c037f1"}, + {file = "tokenizers-0.20.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07d7851a72717321022f3774e84aa9d595a041d643fafa2e87fbc9b18711dac0"}, + {file = "tokenizers-0.20.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bd44e48a430ada902c6266a8245f5036c4fe744fcb51f699999fbe82aa438797"}, + {file = "tokenizers-0.20.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a4c186bb006ccbe1f5cc4e0380d1ce7806f5955c244074fd96abc55e27b77f01"}, + {file = "tokenizers-0.20.3-cp313-none-win32.whl", hash = "sha256:6e19e0f1d854d6ab7ea0c743d06e764d1d9a546932be0a67f33087645f00fe13"}, + {file = "tokenizers-0.20.3-cp313-none-win_amd64.whl", hash = "sha256:d50ede425c7e60966a9680d41b58b3a0950afa1bb570488e2972fa61662c4273"}, + {file = "tokenizers-0.20.3-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:9adda1ff5fb9dcdf899ceca672a4e2ce9e797adb512a6467305ca3d8bfcfbdd0"}, + {file = "tokenizers-0.20.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:6dde2cae6004ba7a3badff4a11911cae03ebf23e97eebfc0e71fef2530e5074f"}, + {file = "tokenizers-0.20.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4a7fd678b35614fca708579eb95b7587a5e8a6d328171bd2488fd9f27d82be4"}, + {file = "tokenizers-0.20.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b80e3c7283a01a356bd2210f53d1a4a5d32b269c2024389ed0173137708d50e"}, + {file = "tokenizers-0.20.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8cc0e8176b762973758a77f0d9c4467d310e33165fb74173418ca3734944da4"}, + {file = "tokenizers-0.20.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5634b2e2f5f3d2b4439d2d74066e22eb4b1f04f3fea05cb2a3c12d89b5a3bcd"}, + {file = "tokenizers-0.20.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b4ba635165bc1ea46f2da8e5d80b5f70f6ec42161e38d96dbef33bb39df73964"}, + {file = "tokenizers-0.20.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e4c7c64172e7789bd8b07aa3087ea87c4c4de7e90937a2aa036b5d92332536"}, + {file = "tokenizers-0.20.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1f74909ef7675c26d4095a817ec3393d67f3158ca4836c233212e5613ef640c4"}, + {file = "tokenizers-0.20.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0e9b81321a1e05b16487d312b4264984513f8b4a7556229cafac6e88c2036b09"}, + {file = "tokenizers-0.20.3-cp37-none-win32.whl", hash = "sha256:ab48184cd58b4a03022a2ec75b54c9f600ffea9a733612c02325ed636f353729"}, + {file = "tokenizers-0.20.3-cp37-none-win_amd64.whl", hash = "sha256:60ac483cebee1c12c71878523e768df02fa17e4c54412966cb3ac862c91b36c1"}, + {file = "tokenizers-0.20.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3229ef103c89583d10b9378afa5d601b91e6337530a0988e17ca8d635329a996"}, + {file = "tokenizers-0.20.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ac52cc24bad3de865c7e65b1c4e7b70d00938a8ae09a92a453b8f676e714ad5"}, + {file = "tokenizers-0.20.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04627b7b502fa6a2a005e1bd446fa4247d89abcb1afaa1b81eb90e21aba9a60f"}, + {file = "tokenizers-0.20.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c27ceb887f0e81a3c377eb4605dca7a95a81262761c0fba308d627b2abb98f2b"}, + {file = "tokenizers-0.20.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65ab780194da4e1fcf5670523a2f377c4838ebf5249efe41fa1eddd2a84fb49d"}, + {file = "tokenizers-0.20.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98d343134f47159e81f7f242264b0eb222e6b802f37173c8d7d7b64d5c9d1388"}, + {file = "tokenizers-0.20.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2475bb004ab2009d29aff13b5047bfdb3d4b474f0aa9d4faa13a7f34dbbbb43"}, + {file = "tokenizers-0.20.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b6583a65c01db1197c1eb36857ceba8ec329d53afadd268b42a6b04f4965724"}, + {file = "tokenizers-0.20.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:62d00ba208358c037eeab7bfc00a905adc67b2d31b68ab40ed09d75881e114ea"}, + {file = "tokenizers-0.20.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0fc7a39e5bedc817bda395a798dfe2d9c5f7c71153c90d381b5135a0328d9520"}, + {file = "tokenizers-0.20.3-cp38-none-win32.whl", hash = "sha256:84d40ee0f8550d64d3ea92dd7d24a8557a9172165bdb986c9fb2503b4fe4e3b6"}, + {file = "tokenizers-0.20.3-cp38-none-win_amd64.whl", hash = "sha256:205a45246ed7f1718cf3785cff88450ba603352412aaf220ace026384aa3f1c0"}, + {file = "tokenizers-0.20.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:93e37f0269a11dc3b1a953f1fca9707f0929ebf8b4063c591c71a0664219988e"}, + {file = "tokenizers-0.20.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f4cb0c614b0135e781de96c2af87e73da0389ac1458e2a97562ed26e29490d8d"}, + {file = "tokenizers-0.20.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7eb2fb1c432f5746b22f8a7f09fc18c4156cb0031c77f53cb19379d82d43297a"}, + {file = "tokenizers-0.20.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfa8d029bb156181b006643309d6b673615a24e4ed24cf03aa191d599b996f51"}, + {file = "tokenizers-0.20.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f90549622de3bf476ad9f1dd6f3f952ec3ed6ab8615ae88ef060d0c5bfad55d"}, + {file = "tokenizers-0.20.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1d469c74eebf5c43fd61cd9b030e271d17198edd7bd45392e03a3c091d7d6d4"}, + {file = "tokenizers-0.20.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bee8f53b2594749f4460d53253bae55d718f04e9b633efa0f5df8938bd98e4f0"}, + {file = "tokenizers-0.20.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:938441babf3e5720e4459e306ef2809fb267680df9d1ff2873458b22aef60248"}, + {file = "tokenizers-0.20.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7310ab23d7b0caebecc0e8be11a1146f320f5f07284000f6ea54793e83de1b75"}, + {file = "tokenizers-0.20.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:16121eb030a2b13094cfec936b0c12e8b4063c5f839591ea7d0212336d8f9921"}, + {file = "tokenizers-0.20.3-cp39-none-win32.whl", hash = "sha256:401cc21ef642ee235985d747f65e18f639464d377c70836c9003df208d582064"}, + {file = "tokenizers-0.20.3-cp39-none-win_amd64.whl", hash = "sha256:7498f3ea7746133335a6adb67a77cf77227a8b82c8483f644a2e5f86fea42b8d"}, + {file = "tokenizers-0.20.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e919f2e3e68bb51dc31de4fcbbeff3bdf9c1cad489044c75e2b982a91059bd3c"}, + {file = "tokenizers-0.20.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b8e9608f2773996cc272156e305bd79066163a66b0390fe21750aff62df1ac07"}, + {file = "tokenizers-0.20.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39270a7050deaf50f7caff4c532c01b3c48f6608d42b3eacdebdc6795478c8df"}, + {file = "tokenizers-0.20.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e005466632b1c5d2d2120f6de8aa768cc9d36cd1ab7d51d0c27a114c91a1e6ee"}, + {file = "tokenizers-0.20.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a07962340b36189b6c8feda552ea1bfeee6cf067ff922a1d7760662c2ee229e5"}, + {file = "tokenizers-0.20.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:55046ad3dd5f2b3c67501fcc8c9cbe3e901d8355f08a3b745e9b57894855f85b"}, + {file = "tokenizers-0.20.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:efcf0eb939988b627558aaf2b9dc3e56d759cad2e0cfa04fcab378e4b48fc4fd"}, + {file = "tokenizers-0.20.3-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f3558a7ae6a6d38a77dfce12172a1e2e1bf3e8871e744a1861cd7591ea9ebe24"}, + {file = "tokenizers-0.20.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d53029fe44bc70c3ff14ef512460a0cf583495a0f8e2f4b70e26eb9438e38a9"}, + {file = "tokenizers-0.20.3-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57a2a56397b2bec5a629b516b23f0f8a3e4f978c7488d4a299980f8375954b85"}, + {file = "tokenizers-0.20.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e5bfaae740ef9ece000f8a07e78ac0e2b085c5ce9648f8593ddf0243c9f76d"}, + {file = "tokenizers-0.20.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fbaf3ea28fedfb2283da60e710aff25492e795a7397cad8a50f1e079b65a5a70"}, + {file = "tokenizers-0.20.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c47c037116310dc976eb96b008e41b9cfaba002ed8005848d4d632ee0b7ba9ae"}, + {file = "tokenizers-0.20.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c31751f0721f58f5e19bb27c1acc259aeff860d8629c4e1a900b26a1979ada8e"}, + {file = "tokenizers-0.20.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:c697cbd3be7a79ea250ea5f380d6f12e534c543cfb137d5c734966b3ee4f34cc"}, + {file = "tokenizers-0.20.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b48971b88ef9130bf35b41b35fd857c3c4dae4a9cd7990ebc7fc03e59cc92438"}, + {file = "tokenizers-0.20.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e615de179bbe060ab33773f0d98a8a8572b5883dd7dac66c1de8c056c7e748c"}, + {file = "tokenizers-0.20.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da1ec842035ed9999c62e45fbe0ff14b7e8a7e02bb97688cc6313cf65e5cd755"}, + {file = "tokenizers-0.20.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6ee4954c1dd23aadc27958dad759006e71659d497dcb0ef0c7c87ea992c16ebd"}, + {file = "tokenizers-0.20.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3eda46ca402751ec82553a321bf35a617b76bbed7586e768c02ccacbdda94d6d"}, + {file = "tokenizers-0.20.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:de082392a85eb0055cc055c535bff2f0cc15d7a000bdc36fbf601a0f3cf8507a"}, + {file = "tokenizers-0.20.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c3db46cc0647bfd88263afdb739b92017a02a87ee30945cb3e86c7e25c7c9917"}, + {file = "tokenizers-0.20.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a292392f24ab9abac5cfa8197e5a6208f2e43723420217e1ceba0b4ec77816ac"}, + {file = "tokenizers-0.20.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dcd91f4e60f62b20d83a87a84fe062035a1e3ff49a8c2bbdeb2d441c8e311f4"}, + {file = "tokenizers-0.20.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:900991a2b8ee35961b1095db7e265342e0e42a84c1a594823d5ee9f8fb791958"}, + {file = "tokenizers-0.20.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5a8d8261ca2133d4f98aa9627c748189502b3787537ba3d7e2beb4f7cfc5d627"}, + {file = "tokenizers-0.20.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c4fd4d71e6deb6ddf99d8d0eab87d1d16f635898906e631914a9bae8ae9f2cfb"}, + {file = "tokenizers-0.20.3.tar.gz", hash = "sha256:2278b34c5d0dd78e087e1ca7f9b1dcbf129d80211afa645f214bd6e051037539"}, +] + +[package.dependencies] +huggingface-hub = ">=0.16.4,<1.0" + +[package.extras] +dev = ["tokenizers[testing]"] +docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] +testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests", "ruff"] + +[[package]] +name = "torch" +version = "2.6.0" +description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" +optional = false +python-versions = ">=3.9.0" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "torch-2.6.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:6860df13d9911ac158f4c44031609700e1eba07916fff62e21e6ffa0a9e01961"}, + {file = "torch-2.6.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c4f103a49830ce4c7561ef4434cc7926e5a5fe4e5eb100c19ab36ea1e2b634ab"}, + {file = "torch-2.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:56eeaf2ecac90da5d9e35f7f35eb286da82673ec3c582e310a8d1631a1c02341"}, + {file = "torch-2.6.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:09e06f9949e1a0518c5b09fe95295bc9661f219d9ecb6f9893e5123e10696628"}, + {file = "torch-2.6.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:7979834102cd5b7a43cc64e87f2f3b14bd0e1458f06e9f88ffa386d07c7446e1"}, + {file = "torch-2.6.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ccbd0320411fe1a3b3fec7b4d3185aa7d0c52adac94480ab024b5c8f74a0bf1d"}, + {file = "torch-2.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:46763dcb051180ce1ed23d1891d9b1598e07d051ce4c9d14307029809c4d64f7"}, + {file = "torch-2.6.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:94fc63b3b4bedd327af588696559f68c264440e2503cc9e6954019473d74ae21"}, + {file = "torch-2.6.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:2bb8987f3bb1ef2675897034402373ddfc8f5ef0e156e2d8cfc47cacafdda4a9"}, + {file = "torch-2.6.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b789069020c5588c70d5c2158ac0aa23fd24a028f34a8b4fcb8fcb4d7efcf5fb"}, + {file = "torch-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e1448426d0ba3620408218b50aa6ada88aeae34f7a239ba5431f6c8774b1239"}, + {file = "torch-2.6.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:9a610afe216a85a8b9bc9f8365ed561535c93e804c2a317ef7fabcc5deda0989"}, + {file = "torch-2.6.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:4874a73507a300a5d089ceaff616a569e7bb7c613c56f37f63ec3ffac65259cf"}, + {file = "torch-2.6.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a0d5e1b9874c1a6c25556840ab8920569a7a4137afa8a63a32cee0bc7d89bd4b"}, + {file = "torch-2.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:510c73251bee9ba02ae1cb6c9d4ee0907b3ce6020e62784e2d7598e0cfa4d6cc"}, + {file = "torch-2.6.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:ff96f4038f8af9f7ec4231710ed4549da1bdebad95923953a25045dcf6fd87e2"}, + {file = "torch-2.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:9ea955317cfcd3852b1402b62af258ce735c2edeee42ca9419b6bc889e5ae053"}, + {file = "torch-2.6.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:bb2c6c3e65049f081940f5ab15c9136c7de40d3f01192541c920a07c7c585b7e"}, + {file = "torch-2.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:683410f97984103148e31b38a8631acf31c3034c020c0f4d26171e7626d8317a"}, + {file = "torch-2.6.0-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:265f70de5fd45b864d924b64be1797f86e76c8e48a02c2a3a6fc7ec247d2226c"}, +] + +[package.dependencies] +filelock = "*" +fsspec = "*" +jinja2 = "*" +networkx = "*" +nvidia-cublas-cu12 = {version = "12.4.5.8", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-cupti-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-nvrtc-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-runtime-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cudnn-cu12 = {version = "9.1.0.70", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufft-cu12 = {version = "11.2.1.3", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-curand-cu12 = {version = "10.3.5.147", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusolver-cu12 = {version = "11.6.1.9", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparse-cu12 = {version = "12.3.1.170", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparselt-cu12 = {version = "0.6.2", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nccl-cu12 = {version = "2.21.5", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvjitlink-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvtx-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +setuptools = {version = "*", markers = "python_version >= \"3.12\""} +sympy = {version = "1.13.1", markers = "python_version >= \"3.9\""} +triton = {version = "3.2.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +typing-extensions = ">=4.10.0" + +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] +optree = ["optree (>=0.13.0)"] + +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "transformers" +version = "4.46.1" +description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "transformers-4.46.1-py3-none-any.whl", hash = "sha256:f77b251a648fd32e3d14b5e7e27c913b7c29154940f519e4c8c3aa6061df0f05"}, + {file = "transformers-4.46.1.tar.gz", hash = "sha256:16d79927d772edaf218820a96f9254e2211f9cd7fb3c308562d2d636c964a68c"}, +] + +[package.dependencies] +accelerate = {version = ">=0.26.0", optional = true, markers = "extra == \"torch\""} +filelock = "*" +huggingface-hub = ">=0.23.2,<1.0" +numpy = ">=1.17" +packaging = ">=20.0" +pyyaml = ">=5.1" +regex = "!=2019.12.17" +requests = "*" +safetensors = ">=0.4.1" +tokenizers = ">=0.20,<0.21" +torch = {version = "*", optional = true, markers = "extra == \"torch\""} +tqdm = ">=4.27" + +[package.extras] +accelerate = ["accelerate (>=0.26.0)"] +agents = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch"] +all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (<=0.9.16)", "tokenizers (>=0.20,<0.21)", "torch", "torchaudio", "torchvision"] +audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +benchmark = ["optimum-benchmark (>=0.3.0)"] +codecarbon = ["codecarbon (==1.2.0)"] +deepspeed = ["accelerate (>=0.26.0)", "deepspeed (>=0.9.3)"] +deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.26.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av (==9.2.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "libcst", "librosa", "nltk (<=3.8.1)", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (<=0.9.16)", "tokenizers (>=0.20,<0.21)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "libcst", "librosa", "nltk (<=3.8.1)", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.20,<0.21)", "urllib3 (<2.0.0)"] +dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "beautifulsoup4", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "libcst", "librosa", "nltk (<=3.8.1)", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm (<=0.9.16)", "tokenizers (>=0.20,<0.21)", "torch", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)", "scipy (<1.13.0)"] +flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +ftfy = ["ftfy"] +integrations = ["optuna", "ray[tune] (>=2.7.0)", "sigopt"] +ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0,<1.3.1)", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] +modelcreation = ["cookiecutter (==1.7.3)"] +natten = ["natten (>=0.14.6,<0.15.0)"] +onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] +onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] +optuna = ["optuna"] +quality = ["GitPython (<3.1.19)", "datasets (!=2.5.0)", "isort (>=5.5.4)", "libcst", "rich", "ruff (==0.5.1)", "urllib3 (<2.0.0)"] +ray = ["ray[tune] (>=2.7.0)"] +retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] +ruff = ["ruff (==0.5.1)"] +sagemaker = ["sagemaker (>=2.31.0)"] +sentencepiece = ["protobuf", "sentencepiece (>=0.1.91,!=0.1.92)"] +serving = ["fastapi", "pydantic", "starlette", "uvicorn"] +sigopt = ["sigopt"] +sklearn = ["scikit-learn"] +speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.5.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +tf = ["keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] +tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<0.24)", "tensorflow-text (<2.16)", "tf2onnx"] +tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +tiktoken = ["blobfile", "tiktoken"] +timm = ["timm (<=0.9.16)"] +tokenizers = ["tokenizers (>=0.20,<0.21)"] +torch = ["accelerate (>=0.26.0)", "torch"] +torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +torch-vision = ["Pillow (>=10.0.1,<=15.0)", "torchvision"] +torchhub = ["filelock", "huggingface-hub (>=0.23.2,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.20,<0.21)", "torch", "tqdm (>=4.27)"] +video = ["av (==9.2.0)"] +vision = ["Pillow (>=10.0.1,<=15.0)"] + +[[package]] +name = "triton" +version = "3.2.0" +description = "A language and compiler for custom Deep Learning operations" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.11\" and platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\" and platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "triton-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3e54983cd51875855da7c68ec05c05cf8bb08df361b1d5b69e05e40b0c9bd62"}, + {file = "triton-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8009a1fb093ee8546495e96731336a33fb8856a38e45bb4ab6affd6dbc3ba220"}, + {file = "triton-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d9b215efc1c26fa7eefb9a157915c92d52e000d2bf83e5f69704047e63f125c"}, + {file = "triton-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5dfa23ba84541d7c0a531dfce76d8bcd19159d50a4a8b14ad01e91734a5c1b0"}, + {file = "triton-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ceed0eff2c4a73b14eb63e052992f44bbdf175f3fad21e1ac8097a772de7ee"}, +] + +[package.extras] +build = ["cmake (>=3.20)", "lit"] +tests = ["autopep8", "flake8", "isort", "llnl-hatchet", "numpy", "pytest", "scipy (>=1.7.1)"] +tutorials = ["matplotlib", "pandas", "tabulate"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "tzdata" +version = "2025.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, + {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "xdis" +version = "6.1.1" +description = "Python cross-version byte-code disassembler and marshal routines" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "xdis-6.1.1-py2-none-any.whl", hash = "sha256:0f7bf67cd9c3a2fa3b14857f05ee159308e4aec97168bbf3cc8db902a14a0e24"}, + {file = "xdis-6.1.1-py310-none-any.whl", hash = "sha256:326bbeb04566eae9c9b679d4fca47262d920edd841fff93ed2efccaf9fec6237"}, + {file = "xdis-6.1.1-py311-none-any.whl", hash = "sha256:37ad24cd9fb72410f1973ea892ca46375b4a9b497bd3b3ed93f440182dfd0e15"}, + {file = "xdis-6.1.1-py312-none-any.whl", hash = "sha256:73365e0645429a3823ee612ae79a2a5a9ed83a0fbd51e1711bbe102884c485d9"}, + {file = "xdis-6.1.1.tar.gz", hash = "sha256:1cbeaf56b996ba1b1e744a3f66753692be5ef9855131b7332601aa0a124e5d13"}, +] + +[package.dependencies] +click = "*" +six = ">=1.10.0" + +[package.extras] +dev = ["pre-commit", "pytest"] + +[[package]] +name = "xxhash" +version = "3.5.0" +description = "Python binding for xxHash" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212"}, + {file = "xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442"}, + {file = "xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da"}, + {file = "xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9"}, + {file = "xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839"}, + {file = "xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da"}, + {file = "xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58"}, + {file = "xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e"}, + {file = "xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8"}, + {file = "xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e"}, + {file = "xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c"}, + {file = "xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637"}, + {file = "xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43"}, + {file = "xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b"}, + {file = "xxhash-3.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6e5f70f6dca1d3b09bccb7daf4e087075ff776e3da9ac870f86ca316736bb4aa"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e76e83efc7b443052dd1e585a76201e40b3411fe3da7af4fe434ec51b2f163b"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33eac61d0796ca0591f94548dcfe37bb193671e0c9bcf065789b5792f2eda644"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ec70a89be933ea49222fafc3999987d7899fc676f688dd12252509434636622"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86b8e7f703ec6ff4f351cfdb9f428955859537125904aa8c963604f2e9d3e7"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0adfbd36003d9f86c8c97110039f7539b379f28656a04097e7434d3eaf9aa131"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:63107013578c8a730419adc05608756c3fa640bdc6abe806c3123a49fb829f43"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:683b94dbd1ca67557850b86423318a2e323511648f9f3f7b1840408a02b9a48c"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5d2a01dcce81789cf4b12d478b5464632204f4c834dc2d064902ee27d2d1f0ee"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:a9d360a792cbcce2fe7b66b8d51274ec297c53cbc423401480e53b26161a290d"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f0b48edbebea1b7421a9c687c304f7b44d0677c46498a046079d445454504737"}, + {file = "xxhash-3.5.0-cp37-cp37m-win32.whl", hash = "sha256:7ccb800c9418e438b44b060a32adeb8393764da7441eb52aa2aa195448935306"}, + {file = "xxhash-3.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c3bc7bf8cb8806f8d1c9bf149c18708cb1c406520097d6b0a73977460ea03602"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:74752ecaa544657d88b1d1c94ae68031e364a4d47005a90288f3bab3da3c970f"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dee1316133c9b463aa81aca676bc506d3f80d8f65aeb0bba2b78d0b30c51d7bd"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:602d339548d35a8579c6b013339fb34aee2df9b4e105f985443d2860e4d7ffaa"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:695735deeddfb35da1677dbc16a083445360e37ff46d8ac5c6fcd64917ff9ade"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1030a39ba01b0c519b1a82f80e8802630d16ab95dc3f2b2386a0b5c8ed5cbb10"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5bc08f33c4966f4eb6590d6ff3ceae76151ad744576b5fc6c4ba8edd459fdec"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160e0c19ee500482ddfb5d5570a0415f565d8ae2b3fd69c5dcfce8a58107b1c3"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f1abffa122452481a61c3551ab3c89d72238e279e517705b8b03847b1d93d738"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d5e9db7ef3ecbfc0b4733579cea45713a76852b002cf605420b12ef3ef1ec148"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:23241ff6423378a731d84864bf923a41649dc67b144debd1077f02e6249a0d54"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:82b833d5563fefd6fceafb1aed2f3f3ebe19f84760fdd289f8b926731c2e6e91"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a80ad0ffd78bef9509eee27b4a29e56f5414b87fb01a888353e3d5bda7038bd"}, + {file = "xxhash-3.5.0-cp38-cp38-win32.whl", hash = "sha256:50ac2184ffb1b999e11e27c7e3e70cc1139047e7ebc1aa95ed12f4269abe98d4"}, + {file = "xxhash-3.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:392f52ebbb932db566973693de48f15ce787cabd15cf6334e855ed22ea0be5b3"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc8cdd7f33d57f0468b0614ae634cc38ab9202c6957a60e31d285a71ebe0301"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0c48b6300cd0b0106bf49169c3e0536408dfbeb1ccb53180068a18b03c662ab"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe1a92cfbaa0a1253e339ccec42dbe6db262615e52df591b68726ab10338003f"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33513d6cc3ed3b559134fb307aae9bdd94d7e7c02907b37896a6c45ff9ce51bd"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eefc37f6138f522e771ac6db71a6d4838ec7933939676f3753eafd7d3f4c40bc"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a606c8070ada8aa2a88e181773fa1ef17ba65ce5dd168b9d08038e2a61b33754"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42eca420c8fa072cc1dd62597635d140e78e384a79bb4944f825fbef8bfeeef6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:604253b2143e13218ff1ef0b59ce67f18b8bd1c4205d2ffda22b09b426386898"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6e93a5ad22f434d7876665444a97e713a8f60b5b1a3521e8df11b98309bff833"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7a46e1d6d2817ba8024de44c4fd79913a90e5f7265434cef97026215b7d30df6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:30eb2efe6503c379b7ab99c81ba4a779748e3830241f032ab46bd182bf5873af"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c8aa771ff2c13dd9cda8166d685d7333d389fae30a4d2bb39d63ab5775de8606"}, + {file = "xxhash-3.5.0-cp39-cp39-win32.whl", hash = "sha256:5ed9ebc46f24cf91034544b26b131241b699edbfc99ec5e7f8f3d02d6eb7fba4"}, + {file = "xxhash-3.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:220f3f896c6b8d0316f63f16c077d52c412619e475f9372333474ee15133a558"}, + {file = "xxhash-3.5.0-cp39-cp39-win_arm64.whl", hash = "sha256:a7b1d8315d9b5e9f89eb2933b73afae6ec9597a258d52190944437158b49d38e"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b4154c00eb22e4d543f472cfca430e7962a0f1d0f3778334f2e08a7ba59363c"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d30bbc1644f726b825b3278764240f449d75f1a8bdda892e641d4a688b1494ae"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fa0b72f2423e2aa53077e54a61c28e181d23effeaafd73fcb9c494e60930c8e"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13de2b76c1835399b2e419a296d5b38dc4855385d9e96916299170085ef72f57"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0691bfcc4f9c656bcb96cc5db94b4d75980b9d5589f2e59de790091028580837"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:297595fe6138d4da2c8ce9e72a04d73e58725bb60f3a19048bc96ab2ff31c692"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1276d369452040cbb943300dc8abeedab14245ea44056a2943183822513a18"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2061188a1ba352fc699c82bff722f4baacb4b4b8b2f0c745d2001e56d0dfb514"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c384c434021e4f62b8d9ba0bc9467e14d394893077e2c66d826243025e1f81"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e6a4dd644d72ab316b580a1c120b375890e4c52ec392d4aef3c63361ec4d77d1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:531af8845aaadcadf951b7e0c1345c6b9c68a990eeb74ff9acd8501a0ad6a1c9"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce379bcaa9fcc00f19affa7773084dd09f5b59947b3fb47a1ceb0179f91aaa1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd1b2281d01723f076df3c8188f43f2472248a6b63118b036e641243656b1b0f"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c770750cc80e8694492244bca7251385188bc5597b6a39d98a9f30e8da984e0"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b150b8467852e1bd844387459aa6fbe11d7f38b56e901f9f3b3e6aba0d660240"}, + {file = "xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f"}, +] + +[[package]] +name = "yarl" +version = "1.18.3" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, + {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, + {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, + {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, + {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, + {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, + {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, + {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, + {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, + {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, + {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, + {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, + {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.0" + +[metadata] +lock-version = "2.1" +python-versions = ">=3.11" +content-hash = "3a5b923fbe837083092ffde6b0fda8d18c82d265318c6da5e89c379513380b31" diff --git a/pylingual/__init__.py b/pylingual/__init__.py new file mode 100644 index 0000000..f3e3eb5 --- /dev/null +++ b/pylingual/__init__.py @@ -0,0 +1,3 @@ +from .decompiler import decompile + +__all__ = ["decompile"] diff --git a/pylingual/control_flow_reconstruction/cfg_utils.py b/pylingual/control_flow_reconstruction/cfg_utils.py new file mode 100644 index 0000000..a6ed9a7 --- /dev/null +++ b/pylingual/control_flow_reconstruction/cfg_utils.py @@ -0,0 +1,58 @@ +import networkx as nx + +from typing import Callable, Any + +from pylingual.editable_bytecode.control_flow_graph import ControlFlowEdgeType + + +def get_out_edge_dict(cfg: nx.DiGraph, node) -> dict: + edge_dict = {"natural": (None, None), "conditional": (None, None), "exception": (None, None)} + if node is None: + return edge_dict + + out_edges = cfg.out_edges(nbunch=node, data=True) + for source, target, edge_props in out_edges: + if edge_props["type"] in [ControlFlowEdgeType.NATURAL.value, ControlFlowEdgeType.JUMP.value]: + edge_dict["natural"] = (target, edge_props) + elif edge_props["type"] in [ControlFlowEdgeType.TRUE_JUMP.value, ControlFlowEdgeType.FALSE_JUMP.value]: + edge_dict["conditional"] = (target, edge_props) + elif edge_props["type"] == ControlFlowEdgeType.EXCEPTION.value: + edge_dict["exception"] = (target, edge_props) + elif edge_props["type"] == ControlFlowEdgeType.META.value: + pass # ignore meta edges in graph traversal + else: + raise ValueError(f"Unknown edge type {edge_props['type']}") + return edge_dict + + +def _to_iter(item): + """Converts something to an iterable version""" + if not hasattr(item, "__iter__") or isinstance(item, str): + return (item,) + return item + + +def create_dominator_tree(graph, start_node=None): + """Creates a dominator tree for the given graph""" + + # default start node is the minimum offset node + if start_node is None: + get_start_offset = lambda node: min(_to_iter(graph.nodes.data()[node].get("offset", ())), default=float("inf")) + start_node = min(graph.nodes, key=get_start_offset) + + dominator_tree = nx.create_empty_copy(graph) + dominator_tree.add_edges_from(nx.immediate_dominators(graph, start_node).items()) + dominator_tree.remove_edge(start_node, start_node) + return dominator_tree.reverse() + + +def get_dominator_function(cfg: nx.DiGraph) -> Callable[[Any, Any], bool]: + # preprocessing to identify loop headers; dominator tree cached in cfg so we don't recompute unless the graph changed + if not hasattr(cfg, "dominator_tree"): + cfg.dominator_tree = create_dominator_tree(cfg, start_node="START") + cfg.domination_relation = nx.transitive_closure_dag(cfg.dominator_tree) + + def dominates(a, b): + return cfg.domination_relation.has_edge(a, b) or a == b + + return dominates diff --git a/pylingual/control_flow_reconstruction/cflow.py b/pylingual/control_flow_reconstruction/cflow.py new file mode 100644 index 0000000..769bfa4 --- /dev/null +++ b/pylingual/control_flow_reconstruction/cflow.py @@ -0,0 +1,57 @@ +import os + +from pylingual.editable_bytecode import EditableBytecode +from pylingual.editable_bytecode.control_flow_graph import bytecode_to_control_flow_graph +from pylingual.utils.use_escape_sequences import use_escape_sequences + +from .structure_control_flow import structure_control_flow + + +def pyc_to_indented_sources(pyc: EditableBytecode, source_lines: list[str]) -> dict[object, str]: + sources = {} + for bytecode in pyc.iter_bytecodes(): + sources[bytecode.codeobj] = bytecode_to_indented_source(bytecode, source_lines) + return sources + + +def split_newlines(li): + return "\n".join(li).split("\n") + + +def bytecode_to_indented_source(bytecode: EditableBytecode, source_lines: list[str]) -> list[str]: + cfg = bytecode_to_control_flow_graph(bytecode) + + # breakpoint to debug control flow templates if DEBUG_CFLOW is set + if os.environ.get("DEBUG_CFLOW", None) == "1": + breakpoint() + + structured = structure_control_flow(cfg, bytecode) + indented_source = structured.to_indented_source(source_lines).split("\n") + + bytecode.ordered_instructions = structured.get_instructions() + # force generator if necessary + if bytecode.codeobj.co_flags & (0x20 | 0x200): + if not any(x.strip().startswith("yield ") or x.strip() == "yield" for x in split_newlines(indented_source)): + indented_source.insert(0, "if False: yield # inserted") + + # insert globals + for global_var in bytecode.globals: + indented_source.insert(0, f"global {global_var} # inserted") + + # insert nonlocals + parent_nonlocal = set() + parent = bytecode.parent + while parent: + parent_nonlocal |= parent.nonlocals + parent = parent.parent + for nonlocal_var in bytecode.nonlocals: + if nonlocal_var in parent_nonlocal: + indented_source.insert(0, f"nonlocal {nonlocal_var} # inserted") + + # add function docstring + if bytecode.codeobj.co_flags & 0x2: + if bytecode.codeobj.co_consts and isinstance(bytecode.codeobj.co_consts[0], str): + doc = use_escape_sequences(bytecode.codeobj.co_consts[0]) + indented_source.insert(0, f'"""{doc}""" # inserted') + + return [line for line in indented_source if line] # filter out empty strings diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/Subgraph.py b/pylingual/control_flow_reconstruction/control_flow_templates/Subgraph.py new file mode 100644 index 0000000..8ff255c --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/Subgraph.py @@ -0,0 +1,171 @@ +import networkx as nx + +from ..cfg_utils import get_out_edge_dict + +from typing import Any, Callable +from .abstract.AbstractTemplate import ControlFlowTemplate +# INVARIANT: each node will have at most one of each "natural", "conditional", and "exception" edge + + +class TemplateEdge: + def __init__(self, source: Any, dest: Any, edge_verification_func: Callable[[Any, Any, dict], bool] = None, commit_none_to_mapping: bool = True) -> None: + self.source = source + self.dest = dest + self.edge_verification_func = edge_verification_func + # for optional edges, toggle if the absence of a node will be committed to the mapping + # set to False for edges that may not exist, even when their template destination may be reachable from other nodes + self.commit_none_to_mapping = commit_none_to_mapping + + def check_edge(self, graph_source: Any, graph_dest: Any, graph_edge_properties: dict) -> bool: + # if no verification function is provided, just check that the edge exists + if self.edge_verification_func is None: + return graph_dest is not None + + return self.edge_verification_func(graph_source, graph_dest, graph_edge_properties) + + +class TemplateNode: + def __init__( + self, node_verification_func: Callable[[nx.DiGraph, Any], bool] = None, natural_edge: TemplateEdge = None, conditional_edge: TemplateEdge = None, exception_edge: TemplateEdge = None, subtemplate: ControlFlowTemplate = None + ) -> None: + self.node_verification_func = node_verification_func + self.natural_edge = natural_edge + self.conditional_edge = conditional_edge + self.exception_edge = exception_edge + self.subtemplate = subtemplate + + def check_node(self, cfg: nx.DiGraph, node: Any) -> bool: + # I am not a valid candidate, so this is not a valid mapping + # it is the job of the node verification func to check in_degree + if self.node_verification_func is None: + if node is None: + return False + elif not self.node_verification_func(cfg, node): + return False + + # check the outgoing edges for this node + node_out_edge_dict = get_out_edge_dict(cfg, node) + natural_target, natural_properties = node_out_edge_dict["natural"] if node_out_edge_dict["natural"] else (None, None) + # if the edge is in the template, it must be valid + if self.natural_edge and not self.natural_edge.check_edge(node, natural_target, natural_properties): + return False + # if the edge is not in the template, reject + if natural_target and not self.natural_edge: + return False + + conditional_target, conditional_properties = node_out_edge_dict["conditional"] if node_out_edge_dict["conditional"] else (None, None) + # if the edge is in the template, it must be valid + if self.conditional_edge and not self.conditional_edge.check_edge(node, conditional_target, conditional_properties): + return False + # if the edge is not in the template, reject + if conditional_target and not self.conditional_edge: + return False + + exception_target, exception_properties = node_out_edge_dict["exception"] if node_out_edge_dict["exception"] else (None, None) + # if the edge is in the template, it must be valid + if self.exception_edge and not self.exception_edge.check_edge(node, exception_target, exception_properties): + return False + # if the edge is not in the template, reject + if exception_target and not self.exception_edge: + return False + + # node is good and all outgoing edges are good + return True + + +class GraphTemplateMatcher: + def __init__(self, template_node_dict: dict[Any, TemplateNode], root_key: Any, mapping_verification_func: Callable[[nx.DiGraph, dict], bool]) -> None: + self.template_node_dict = template_node_dict + self.root_key = root_key + self.mapping_verification_func = mapping_verification_func + + def match_at_graph_node(self, cfg: nx.DiGraph, root_node: Any) -> dict: + mapping = dict() + mapped_nodes = set() + + dfs_stack = [(self.root_key, root_node)] + + original_cfg = cfg # save this reference for later + + while dfs_stack: + current_template_key, current_graph_node = dfs_stack.pop() + current_template_node = self.template_node_dict[current_template_key] + # if the template node has already been mapped, we don't process it again + if current_template_key in mapping: + # if the current template node has been mapped inconsistently, then the mapping failed + if mapping[current_template_key] != current_graph_node: + return None + else: + continue + if current_graph_node in mapped_nodes: + return None + + # try to match the node subtemplate if one was provided + # if there is a match, then update the cfg under consideration, ensuring that nodes don't get double-mapped + if current_template_node.subtemplate: + updated_cfg = current_template_node.subtemplate.try_to_match_node(cfg, current_graph_node) + # if we didn't match the subtemplate, then this node matching failed + if not updated_cfg: + return None + + # check that previously mapped nodes did not get removed + for mapped_node in mapping.values(): + if mapped_node is not None and mapped_node not in updated_cfg.nodes: + return None + + # update the current graph node + added_nodes = set(updated_cfg.nodes) - set(cfg.nodes) + # enforce invariant that templates add no more than one node + assert len(added_nodes) <= 1 + if added_nodes: + current_graph_node = added_nodes.pop() + + # update the cfg + cfg = updated_cfg + + # if the node is not a valid match, then the mapping failed + # check_node also checks all the outgoing edges + if not current_template_node.check_node(cfg, current_graph_node): + return None + + mapping[current_template_key] = current_graph_node + mapped_nodes.add(current_graph_node) + + graph_node_out_edge_dict = get_out_edge_dict(cfg, current_graph_node) + + # extend along the natural edge + if current_template_node.natural_edge: + next_template_key = current_template_node.natural_edge.dest + if next_template_key is not None: + next_graph_node, _ = graph_node_out_edge_dict["natural"] if graph_node_out_edge_dict["natural"] else (None, None) + if next_graph_node is not None or current_template_node.natural_edge.commit_none_to_mapping: + dfs_stack.append((next_template_key, next_graph_node)) + + # extend along the conditional edge + if current_template_node.conditional_edge: + next_template_key = current_template_node.conditional_edge.dest + if next_template_key is not None: + next_graph_node, _ = graph_node_out_edge_dict["conditional"] if graph_node_out_edge_dict["conditional"] else (None, None) + if next_graph_node is not None or current_template_node.conditional_edge.commit_none_to_mapping: + dfs_stack.append((next_template_key, next_graph_node)) + + # extend along the exception edge + if current_template_node.exception_edge: + next_template_key = current_template_node.exception_edge.dest + if next_template_key is not None: + next_graph_node, _ = graph_node_out_edge_dict["exception"] if graph_node_out_edge_dict["exception"] else (None, None) + if next_graph_node is not None or current_template_node.exception_edge.commit_none_to_mapping: + dfs_stack.append((next_template_key, next_graph_node)) + + # we have a final mapping, check any top-level verification stuff + if self.mapping_verification_func and not self.mapping_verification_func(cfg, mapping): + return None + + # mapping was successful + if cfg == original_cfg: + return mapping + + # commit changes to the original cfg by modifying the reference + original_cfg.clear() + original_cfg.update(cfg) + return mapping diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractExceptionBlockTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractExceptionBlockTemplate.py new file mode 100644 index 0000000..30f4bd5 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractExceptionBlockTemplate.py @@ -0,0 +1,5 @@ +# to make a base template for exception blocks + + +class AbstractExceptionBlockTemplate: + pass diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractNonSequentiableTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractNonSequentiableTemplate.py new file mode 100644 index 0000000..71550b3 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractNonSequentiableTemplate.py @@ -0,0 +1,5 @@ +# to make a base template to deal with end finallys so we can whitelist templates + + +class AbstractNonSequentiable: + pass diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractTemplate.py new file mode 100644 index 0000000..32599aa --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/abstract/AbstractTemplate.py @@ -0,0 +1,42 @@ +from abc import ABC, abstractmethod + +import networkx as nx + +from pylingual.editable_bytecode import Inst + + +class ControlFlowTemplate(ABC): + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + ... + + @abstractmethod + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + ... + + @staticmethod + def _indent_multiline_string(multiline_string: str, indentation_level: int = 1) -> str: + return "\n".join("\t" * indentation_level + line.rstrip() for line in multiline_string.split("\n") if line) + + def __repr__(self) -> str: + name = f"{type(self).__name__}" + components = ControlFlowTemplate._indent_multiline_string(",\n".join(f"{key}={repr(value)}" for key, value in vars(self).items())) + return f"{name}[\n{components}]" + + def get_instructions(self) -> list[Inst]: + insts: list[Inst] = [] + for key, value in vars(self).items(): + if hasattr(value, "get_instructions"): + insts.extend(value.get_instructions()) + elif isinstance(value, Inst): + insts.append(value) + return insts + return sorted(insts, key=lambda i: i.offset) diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ChainedComparisonTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ChainedComparisonTemplate.py new file mode 100644 index 0000000..0fc263c --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ChainedComparisonTemplate.py @@ -0,0 +1,321 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_node_has_no_backwards_edges, node_match_all, assert_no_linestarts + + + +class ChainedComparisonTemplate(ControlFlowTemplate): + """ + A chained comparison such as a == b == c. + (0) + / \\j + (1) (2) (0123) + / \\j/j / \\j + (3) (5) --> (4) (5) + |j + (4) + + not (a == b == c) + + (0) + j/ \\ + (2) (1) --> (0123) + / / \\j / \\j + | (3) (5) (4) (5) + | /j + (4) + + optionally, all nodes in the pattern can have a shared exception handler. + This condenses the chained comparison down to be matched against an if-like template later + """ + + _subgraph = { + "first_condition": TemplateNode( + node_verification_func=assert_node_has_no_backwards_edges, + natural_edge=TemplateEdge( + source="first_condition", + dest="second_condition", + ), + conditional_edge=TemplateEdge( + source="first_condition", + dest="cleanup", + ), + exception_edge=TemplateEdge( + source="first_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "second_condition": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + assert_no_linestarts, + ), + natural_edge=TemplateEdge( + source="second_condition", + dest="j2if_body", + ), + conditional_edge=TemplateEdge( + source="second_condition", + dest="tail", + ), + exception_edge=TemplateEdge( + source="second_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "cleanup": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + assert_no_linestarts, + ), + natural_edge=TemplateEdge( + source="cleanup", + dest="tail", + ), + exception_edge=TemplateEdge( + source="cleanup", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "j2if_body": TemplateNode( + natural_edge=TemplateEdge( + source="j2if_body", + dest="if_body", + ), + exception_edge=TemplateEdge( + source="j2if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + natural_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + _subgraph2 = { + "first_condition": TemplateNode( + node_verification_func=assert_node_has_no_backwards_edges, + natural_edge=TemplateEdge( + source="first_condition", + dest="second_condition", + ), + conditional_edge=TemplateEdge( + source="first_condition", + dest="cleanup", + ), + exception_edge=TemplateEdge( + source="first_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "second_condition": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + assert_no_linestarts, + ), + natural_edge=TemplateEdge( + source="second_condition", + dest="j2if_body", + ), + conditional_edge=TemplateEdge( + source="second_condition", + dest="tail", + ), + exception_edge=TemplateEdge( + source="second_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "cleanup": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + assert_no_linestarts, + ), + natural_edge=TemplateEdge( + source="cleanup", + dest="if_body", + ), + exception_edge=TemplateEdge( + source="j2if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "j2if_body": TemplateNode( + natural_edge=TemplateEdge( + source="j2if_body", + dest="if_body", + ), + exception_edge=TemplateEdge( + source="j2if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + natural_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, first_condition: ControlFlowTemplate, second_condition: ControlFlowTemplate, cleanup: ControlFlowTemplate, j2if_body: ControlFlowTemplate): + self.first_condition = first_condition + self.second_condition = second_condition + self.cleanup = cleanup + self.j2if_body = j2if_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ChainedComparisonTemplate._subgraph, root_key="first_condition", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + matcher = GraphTemplateMatcher(template_node_dict=ChainedComparisonTemplate._subgraph2, root_key="first_condition", mapping_verification_func=None) + mapping = matcher.match_at_graph_node(cfg, node) + if not mapping: + return None + + chained_comparison_template = ChainedComparisonTemplate(first_condition=mapping["first_condition"], second_condition=mapping["second_condition"], cleanup=mapping["cleanup"], j2if_body=mapping["j2if_body"]) + + in_edges = ((src, chained_comparison_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [(chained_comparison_template, mapping["if_body"], {"type": ControlFlowEdgeType.NATURAL.value}), (chained_comparison_template, mapping["tail"], {"type": ControlFlowEdgeType.TRUE_JUMP.value})] + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([chained_comparison_template.first_condition, chained_comparison_template.second_condition, chained_comparison_template.cleanup, chained_comparison_template.j2if_body]) + reduced_cfg.add_node(chained_comparison_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + first_condition = self.first_condition.to_indented_source(source_lines) + second_condition = self.second_condition.to_indented_source(source_lines) + return "\n".join([first_condition, second_condition]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitAndTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitAndTemplate.py new file mode 100644 index 0000000..a9778d0 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitAndTemplate.py @@ -0,0 +1,274 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..loop.LoopExitTemplate import LoopExitTemplate + + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_node_has_no_backwards_edges, node_match_all, assert_no_linestarts, assert_node_type, node_match_none + +from ..loop.PreRefinedLoopTemplate import PreRefinedLoopTemplate + + +class ShortCircuitAndTemplate(ControlFlowTemplate): + """ + A short-circuit evaluated boolean AND. Typically these are all part of one line. + (0) + / \\ (01) + (1) |j --> / \\j + |\\j| (2) (3) + (2) (3) + + optionally, all nodes in the pattern can have a shared exception handler. + This condenses the short-circuit down to be matched against an if-like template later + """ + + _subgraph = { + "first_condition": TemplateNode( + node_verification_func=assert_node_has_no_backwards_edges, + natural_edge=TemplateEdge( + source="first_condition", + dest="second_condition", + ), + conditional_edge=TemplateEdge( + source="first_condition", + dest="tail", + ), + exception_edge=TemplateEdge( + source="first_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "second_condition": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + assert_no_linestarts, + node_match_none(assert_node_type(PreRefinedLoopTemplate)), + ), + natural_edge=TemplateEdge( + source="second_condition", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="second_condition", + dest="tail", + ), + exception_edge=TemplateEdge( + source="second_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + natural_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + _subgraph_for_loop_exits = { + "first_condition": TemplateNode( + node_verification_func=assert_node_has_no_backwards_edges, + natural_edge=TemplateEdge( + source="first_condition", + dest="second_condition", + ), + conditional_edge=TemplateEdge( + source="first_condition", + dest="first_loop_exit_tail", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "second_condition": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + assert_no_linestarts, + ), + natural_edge=TemplateEdge( + source="second_condition", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="second_condition", + dest="second_loop_exit_tail", + ), + exception_edge=TemplateEdge( + source="second_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + natural_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "first_loop_exit_tail": TemplateNode( + node_verification_func=assert_node_type(LoopExitTemplate), + exception_edge=TemplateEdge( + source="first_loop_exit_tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "second_loop_exit_tail": TemplateNode( + node_verification_func=assert_node_type(LoopExitTemplate), + exception_edge=TemplateEdge( + source="second_loop_exit_tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def _verify_loop_exit_match(cfg: nx.DiGraph, mapping: dict) -> bool: + first_tail = mapping["first_loop_exit_tail"] + second_tail = mapping["second_loop_exit_tail"] + if not isinstance(first_tail, LoopExitTemplate) or not isinstance(second_tail, LoopExitTemplate): + return False + + # the loop exits should have no code associated with them + # this part of the pattern is just to deal with implicit continues that got split into separate nodes + if first_tail.tail or second_tail.tail: + return False + + return first_tail.exit_statement == second_tail.exit_statement + + def __init__(self, first_condition: ControlFlowTemplate, second_condition: ControlFlowTemplate): + self.first_condition = first_condition + self.second_condition = second_condition + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ShortCircuitAndTemplate._subgraph, root_key="first_condition", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + matcher = GraphTemplateMatcher(template_node_dict=ShortCircuitAndTemplate._subgraph_for_loop_exits, root_key="first_condition", mapping_verification_func=ShortCircuitAndTemplate._verify_loop_exit_match) + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + short_circuit_template = ShortCircuitAndTemplate( + first_condition=mapping["first_condition"], + second_condition=mapping["second_condition"], + ) + + in_edges = ((src, short_circuit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [(short_circuit_template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(short_circuit_template.second_condition, data=True)] + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([short_circuit_template.first_condition, short_circuit_template.second_condition]) + if first_loop_exit_tail := mapping.get("first_loop_exit_tail", None): + reduced_cfg.remove_node(first_loop_exit_tail) + reduced_cfg.add_node(short_circuit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + first_condition = self.first_condition.to_indented_source(source_lines) + second_condition = self.second_condition.to_indented_source(source_lines) + return "\n".join([first_condition, second_condition]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitOrContinueTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitOrContinueTemplate.py new file mode 100644 index 0000000..fa5af56 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitOrContinueTemplate.py @@ -0,0 +1,188 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import get_dominator_function + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_unconditional_jump + + + +class ShortCircuitOrContinueTemplate(ControlFlowTemplate): + """ + A short-circuit evaluated boolean OR. Typically these are all part of one line. + This variant occurs only when the content of the if statement is just a continue. + + (-1) + | \\ + (0) \\ + / \\j | (-10) + (1) (2) |j --> / \\j + \\j / (1) (2) + (3) -/ \\j + (3) + + optionally, all nodes in the pattern can have a shared exception handler. + This condenses the short-circuit down to be matched against an if-like template later + """ + + _subgraph = { + "first_condition": TemplateNode( + natural_edge=TemplateEdge( + source="first_condition", + dest="second_condition", + ), + conditional_edge=TemplateEdge( + source="first_condition", + dest="loop_header", + ), + exception_edge=TemplateEdge( + source="first_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "second_condition": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="second_condition", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="second_condition", + dest="tail", + ), + exception_edge=TemplateEdge( + source="second_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + node_verification_func=assert_unconditional_jump, # this is the continue statement + natural_edge=TemplateEdge( + source="if_body", + dest="loop_header", + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "loop_header": TemplateNode( + natural_edge=TemplateEdge( + source="loop_header", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="loop_header", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="loop_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, first_condition: ControlFlowTemplate, second_condition: ControlFlowTemplate): + self.first_condition = first_condition + self.second_condition = second_condition + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + if cfg.in_degree(node) != 1: + return None + + # to avoid being treated as an if-else, we actually need to greedily search up one layer + pred = next(cfg.predecessors(node)) + + def verify_loop_header(cfg: nx.DiGraph, mapping: dict[str, ControlFlowTemplate]) -> bool: + # check to make sure that all non-stack/control instructions match between the two finally blocks + # this list was made for 3.9, so it may need to be expanded for other versions + dominates = get_dominator_function(cfg) + return dominates(mapping["loop_header"], mapping["first_condition"]) + + matcher = GraphTemplateMatcher(template_node_dict=ShortCircuitOrContinueTemplate._subgraph, root_key="first_condition", mapping_verification_func=verify_loop_header) + + mapping = matcher.match_at_graph_node(cfg, pred) + + if not mapping: + return None + + short_circuit_template = ShortCircuitOrContinueTemplate( + first_condition=mapping["first_condition"], + second_condition=mapping["second_condition"], + ) + + in_edges = ((src, short_circuit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=short_circuit_template.first_condition, data=True)) + out_edges = [(short_circuit_template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(short_circuit_template.second_condition, data=True)] + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([short_circuit_template.first_condition, short_circuit_template.second_condition]) + reduced_cfg.add_node(short_circuit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + first_condition = self.first_condition.to_indented_source(source_lines) + second_condition = self.second_condition.to_indented_source(source_lines) + return "\n".join([first_condition, second_condition]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitOrTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitOrTemplate.py new file mode 100644 index 0000000..b8f153d --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/booleans/ShortCircuitOrTemplate.py @@ -0,0 +1,157 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree + + + +class ShortCircuitOrTemplate(ControlFlowTemplate): + """ + A short-circuit evaluated boolean OR. Typically these are all part of one line. + (0) + / \\ (01) + (1) |j --> / \\j + |j\\| (2) (3) + (2) (3) + + optionally, all nodes in the pattern can have a shared exception handler. + This condenses the short-circuit down to be matched against an if-like template later + """ + + _subgraph = { + "first_condition": TemplateNode( + natural_edge=TemplateEdge( + source="first_condition", + dest="second_condition", + ), + conditional_edge=TemplateEdge( + source="first_condition", + dest="if_body", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "second_condition": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="second_condition", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="second_condition", + dest="tail", + ), + exception_edge=TemplateEdge( + source="second_condition", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + natural_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="if_body", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, first_condition: ControlFlowTemplate, second_condition: ControlFlowTemplate): + self.first_condition = first_condition + self.second_condition = second_condition + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ShortCircuitOrTemplate._subgraph, root_key="first_condition", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + short_circuit_template = ShortCircuitOrTemplate( + first_condition=mapping["first_condition"], + second_condition=mapping["second_condition"], + ) + + in_edges = ((src, short_circuit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [(short_circuit_template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(short_circuit_template.second_condition, data=True)] + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([short_circuit_template.first_condition, short_circuit_template.second_condition]) + reduced_cfg.add_node(short_circuit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + first_condition = self.first_condition.to_indented_source(source_lines) + second_condition = self.second_condition.to_indented_source(source_lines) + return "\n".join([first_condition, second_condition]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/AsyncWithCleanup312.py b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/AsyncWithCleanup312.py new file mode 100644 index 0000000..b4792ff --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/AsyncWithCleanup312.py @@ -0,0 +1,130 @@ +import networkx as nx + +import itertools + + +from ..abstract.AbstractTemplate import ControlFlowTemplate + + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_edge, assert_in_degree, node_match_all, is_exactly_opname, contains_opname_sequence + + +class AsyncWithCleanup312(ControlFlowTemplate): + _subgraph = { + "start": TemplateNode( + node_verification_func=is_exactly_opname("PUSH_EXC_INFO", "WITH_EXCEPT_START", "GET_AWAITABLE", "LOAD_CONST"), + natural_edge=TemplateEdge( + source="start", + dest="send", + ), + exception_edge=TemplateEdge(source="start", dest="exc"), + ), + "send": TemplateNode( + node_verification_func=node_match_all(is_exactly_opname("SEND"), assert_in_degree(2)), + natural_edge=TemplateEdge( + source="send", + dest="yield", + ), + conditional_edge=TemplateEdge( + source="send", + dest="ifthen", + ), + exception_edge=TemplateEdge( + source="send", + dest="exc", + ), + ), + "yield": TemplateNode( + node_verification_func=node_match_all(is_exactly_opname("YIELD_VALUE"), assert_in_degree(1)), natural_edge=TemplateEdge(source="yield", dest="jump_back"), exception_edge=TemplateEdge(source="yield", dest="ifthen") + ), + "jump_back": TemplateNode( + node_verification_func=node_match_all(is_exactly_opname("JUMP_BACKWARD_NO_INTERRUPT"), assert_in_degree(1)), natural_edge=TemplateEdge(source="jump_back", dest="send"), exception_edge=TemplateEdge(source="jump_back", dest="exc") + ), + "ifthen": TemplateNode( + node_verification_func=node_match_all(is_exactly_opname("CLEANUP_THROW", "END_SEND", "POP_JUMP_IF_TRUE", "RERAISE", "POP_TOP"), assert_in_degree(2)), + natural_edge=TemplateEdge(source="ifthen", dest="tail"), + exception_edge=TemplateEdge( + source="ifthen", + dest="exc", + ), + ), + "exc": TemplateNode( + node_verification_func=node_match_all(is_exactly_opname("COPY", "POP_EXCEPT", "RERAISE"), assert_in_degree(4)), + natural_edge=TemplateEdge( + source="exc", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exc", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exc", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + node_verification_func=node_match_all(contains_opname_sequence("POP_EXCEPT", "POP_TOP", "POP_TOP"), assert_in_degree(1)), + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self): + pass + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher( + template_node_dict=AsyncWithCleanup312._subgraph, + root_key="start", + mapping_verification_func=None, + ) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + with_template = AsyncWithCleanup312() + + in_edges = ((src, with_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + # out_edges = ((with_template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(nbunch=mapping['exc'], data=True)) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from(mapping.values()) + reduced_cfg.add_node(with_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + return "" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/Await312Template.py b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/Await312Template.py new file mode 100644 index 0000000..7dfba4c --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/Await312Template.py @@ -0,0 +1,136 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_edge, assert_in_degree, assert_node_has_no_backwards_edges, node_match_all, is_exactly_opname, node_match_any +from ...cfg_utils import ControlFlowEdgeType + + +class Await312Template(ControlFlowTemplate): + _subgraph = { + "awaited": TemplateNode( + natural_edge=TemplateEdge( + source="awaited", + dest="send", + ), + exception_edge=TemplateEdge( + source="awaited", + dest="exception_handler", + ), + ), + "send": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(2), assert_node_has_no_backwards_edges, is_exactly_opname("SEND")), + natural_edge=TemplateEdge( + source="send", + dest="yield", + ), + conditional_edge=TemplateEdge( + source="send", + dest="jump_back", + ), + exception_edge=TemplateEdge( + source="send", + dest="exception_handler", + ), + ), + "yield": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1), assert_node_has_no_backwards_edges, is_exactly_opname("YIELD_VALUE")), + natural_edge=TemplateEdge( + source="yield", + dest="jump_back", + ), + exception_edge=TemplateEdge( + source="yield", + dest="cleanup_throw", + ), + ), + "jump_back": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(2), is_exactly_opname("JUMP_BACKWARD_NO_INTERRUPT")), + natural_edge=TemplateEdge( + source="jump_back", + dest="send", + ), + exception_edge=TemplateEdge( + source="jump_back", + dest="exception_handler", + ), + ), + "cleanup_throw": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1), is_exactly_opname("CLEANUP_THROW")), + natural_edge=TemplateEdge( + source="cleanup_throw", + dest="jump_back2", + ), + exception_edge=TemplateEdge( + source="cleanup_throw", + dest="exception_handler", + ), + ), + "jump_back2": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1), node_match_any(is_exactly_opname("JUMP_BACKWARD"), is_exactly_opname("JUMP_BACKWARD_NO_INTERRUPT"))), + natural_edge=TemplateEdge( + source="jump_back2", + dest=None, + ), + ), + "exception_handler": TemplateNode( + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, awaited): + self.awaited = awaited + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=Await312Template._subgraph, root_key="awaited", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + template = Await312Template( + awaited=mapping["awaited"], + ) + + in_edges = ((src, template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [(template, next(cfg.successors(mapping["jump_back2"])), {"type": ControlFlowEdgeType.NATURAL.value}), (template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})] + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([template.awaited, mapping["send"], mapping["yield"], mapping["jump_back"], mapping["cleanup_throw"], mapping["jump_back2"]]) + reduced_cfg.add_node(template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + return self.awaited.to_indented_source(source_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithCleanup312.py b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithCleanup312.py new file mode 100644 index 0000000..0574ff8 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithCleanup312.py @@ -0,0 +1,128 @@ +import networkx as nx + + + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable + + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_edge, assert_in_degree, node_match_all, is_exactly_opname, contains_opname_sequence, node_match_any + + +class WithCleanup312(ControlFlowTemplate, AbstractNonSequentiable): + _subgraph = { + "start": TemplateNode( + node_verification_func=node_match_any( + is_exactly_opname("PUSH_EXC_INFO", "WITH_EXCEPT_START", "POP_JUMP_IF_TRUE"), + is_exactly_opname("PUSH_EXC_INFO", "WITH_EXCEPT_START", "TO_BOOL", "POP_JUMP_IF_TRUE"), + ), + natural_edge=TemplateEdge( + source="start", + dest="reraise", + ), + conditional_edge=TemplateEdge( + source="start", + dest="poptop", + ), + exception_edge=TemplateEdge(source="start", dest="exc"), + ), + "reraise": TemplateNode( + node_verification_func=node_match_all(is_exactly_opname("RERAISE"), assert_in_degree(1)), + exception_edge=TemplateEdge( + source="reraise", + dest="exc", + ), + ), + "poptop": TemplateNode( + node_verification_func=node_match_all(is_exactly_opname("POP_TOP"), assert_in_degree(1)), + natural_edge=TemplateEdge( + source="poptop", + dest="tail", + ), + exception_edge=TemplateEdge( + source="poptop", + dest="exc", + ), + ), + "exc": TemplateNode( + node_verification_func=node_match_all(is_exactly_opname("COPY", "POP_EXCEPT", "RERAISE"), assert_in_degree(3)), + natural_edge=TemplateEdge( + source="exc", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exc", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exc", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + node_verification_func=node_match_all(contains_opname_sequence("POP_EXCEPT", "POP_TOP", "POP_TOP"), assert_in_degree(1)), + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self): + pass + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + # to avoid being treated as an try-except, we actually need to greedily search up one layer + node = next(cfg.predecessors(node)) + + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher( + template_node_dict=WithCleanup312._subgraph, + root_key="start", + mapping_verification_func=None, + ) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + with_template = WithCleanup312() + + in_edges = ((src, with_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from(mapping.values()) + reduced_cfg.add_node(with_template) + reduced_cfg.add_edges_from(in_edges) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + return "" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate.py new file mode 100644 index 0000000..f286f5f --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate.py @@ -0,0 +1,177 @@ +import networkx as nx + +import itertools + + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_with, node_match_all +from ..subtemplates.OptionalExitSubtemplate import ExitSubTemplate + + +class WithTemplate(ControlFlowTemplate, AbstractNonSequentiable): + r""" + + A basic with template as a catch for normal withs + + (0) node 2 may point to an outer exception handler + | + (1) + e/ | + / (2) + \ | + (3) + """ + + _subgraph = { + "setup_with": TemplateNode( + node_verification_func=assert_with, + natural_edge=TemplateEdge( + source="setup_with", + dest="body", + ), + exception_edge=TemplateEdge( + source="setup_with", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="body", + dest="begin_finally", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, # since it is possible to not have a begin finally block we need to commit it to mapping + ), + exception_edge=TemplateEdge(source="body", dest="with_cleanup"), + ), + "begin_finally": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=node_match_all( + optional_node, + assert_in_degree(1), + ), + natural_edge=TemplateEdge( + source="begin_finally", + dest="with_cleanup", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, # if the destination node is None, don't commit to the mapping + ), + exception_edge=TemplateEdge(source="begin_finally", dest="exception_handler", edge_verification_func=optional_edge), + ), + "with_cleanup": TemplateNode( + natural_edge=TemplateEdge( + source="with_cleanup", + dest="tail", + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="with_cleanup", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, setup_with: ControlFlowTemplate, body: ControlFlowTemplate, begin_finally: ControlFlowTemplate, with_cleanup: ControlFlowTemplate): + self.setup_with = setup_with + self.body = body + self.begin_finally = begin_finally + self.with_cleanup = with_cleanup + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + # to avoid being treated as an try-except, we actually need to greedily search up one layer + node = next(cfg.predecessors(node)) + + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher( + template_node_dict=WithTemplate._subgraph, + root_key="setup_with", + mapping_verification_func=None, + ) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + with_template = WithTemplate( + setup_with=mapping["setup_with"], + body=mapping["body"], + begin_finally=mapping.get("begin_finally", None), + with_cleanup=mapping["with_cleanup"], + ) + + in_edges = ((src, with_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [] + if mapping["tail"]: + out_edges.append((with_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})) + else: + out_edges.extend([(with_template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(nbunch=mapping["with_cleanup"], data=True)]) + if mapping["exception_handler"]: + out_edges.append((with_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([with_template.setup_with, with_template.body, with_template.begin_finally, with_template.with_cleanup]) + reduced_cfg.add_node(with_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + header = self.setup_with.to_indented_source(source_lines) + body = self.body._indent_multiline_string(self.body.to_indented_source(source_lines)) + # cleanup = self.with_cleanup.to_indented_source(source_lines) + return f"{header}\n{body}" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate312.py b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate312.py new file mode 100644 index 0000000..17c819b --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate312.py @@ -0,0 +1,124 @@ +import networkx as nx + +import itertools + + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher +from .WithCleanup312 import WithCleanup312 +from .AsyncWithCleanup312 import AsyncWithCleanup312 + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_with, node_match_all, assert_node_type + + +class WithTemplate312(ControlFlowTemplate, AbstractNonSequentiable): + _subgraph = { + "setup_with": TemplateNode( + node_verification_func=assert_with, + natural_edge=TemplateEdge( + source="setup_with", + dest="body", + ), + exception_edge=TemplateEdge( + source="setup_with", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="body", dest="with_cleanup2", edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="body", + dest="with_cleanup", + ), + ), + "with_cleanup": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1), assert_node_type(WithCleanup312, AsyncWithCleanup312)), + ), + "with_cleanup2": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="with_cleanup2", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="with_cleanup2", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="with_cleanup2", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, setup_with: ControlFlowTemplate, body: ControlFlowTemplate, with_cleanup: ControlFlowTemplate, with_cleanup2: ControlFlowTemplate): + self.setup_with = setup_with + self.body = body + self.with_cleanup = with_cleanup + self.with_cleanup2 = with_cleanup2 + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + node = next(cfg.predecessors(node)) + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher( + template_node_dict=WithTemplate312._subgraph, + root_key="setup_with", + mapping_verification_func=None, + ) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + with_template = WithTemplate312( + setup_with=mapping["setup_with"], + body=mapping["body"], + with_cleanup=mapping["with_cleanup"], + with_cleanup2=mapping.get("with_cleanup2"), + ) + + in_edges = ((src, with_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + if "with_cleanup2" in mapping: + out_edges = ( + (with_template, dst, {"type": ControlFlowEdgeType.NATURAL.value} if edge_properties["type"] == ControlFlowEdgeType.JUMP.value else edge_properties) + for src, dst, edge_properties in cfg.out_edges(nbunch=mapping["with_cleanup2"], data=True) + ) + else: + out_edges = () + out_edges2 = ((with_template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(nbunch=mapping["setup_with"], data=True) if edge_properties["type"] == ControlFlowEdgeType.EXCEPTION.value) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([with_template.setup_with, with_template.body, with_template.with_cleanup, with_template.with_cleanup2]) + reduced_cfg.add_node(with_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges, out_edges2)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + header = self.setup_with.to_indented_source(source_lines) + body = self._indent_multiline_string(self.body.to_indented_source(source_lines)) + if self.with_cleanup2 is not None: + clean = self.with_cleanup2.to_indented_source(source_lines) + else: + clean = "" + return f"{header}\n{body}\n{clean}" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate39.py b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate39.py new file mode 100644 index 0000000..56e6303 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/context_managers/WithTemplate39.py @@ -0,0 +1,115 @@ +import networkx as nx + +import itertools + + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ..try_except.TryExceptTemplate import TryExceptTemplate + + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_with, node_match_all, assert_node_type + + +class WithTemplate39(ControlFlowTemplate, AbstractNonSequentiable): + _subgraph = { + "setup_with": TemplateNode( + node_verification_func=assert_with, + natural_edge=TemplateEdge( + source="setup_with", + dest="body", + ), + exception_edge=TemplateEdge( + source="setup_with", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "body": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1), assert_node_type(TryExceptTemplate)), + natural_edge=TemplateEdge( + source="body", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="body", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="body", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, setup_with: ControlFlowTemplate, body: ControlFlowTemplate): + self.setup_with = setup_with + self.body = body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + node = next(cfg.predecessors(node)) + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher( + template_node_dict=WithTemplate39._subgraph, + root_key="setup_with", + mapping_verification_func=None, + ) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + with_template = WithTemplate39( + setup_with=mapping["setup_with"], + body=mapping["body"], + ) + + in_edges = ((src, with_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = ((with_template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(nbunch=mapping["body"], data=True)) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([with_template.setup_with, with_template.body]) + reduced_cfg.add_node(with_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + __import__("pdb").set_trace() + header = self.setup_with.to_indented_source(source_lines) + body = self._indent_multiline_string(self.body.try_body.to_indented_source(source_lines)) + return f"{header}\n{body}" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ElseExitExceptTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ElseExitExceptTemplate.py new file mode 100644 index 0000000..595009f --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ElseExitExceptTemplate.py @@ -0,0 +1,180 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree + + +class ElseExitExceptTemplate(ControlFlowTemplate, AbstractNonSequentiable): + """ + An if-else block where only the else has no further control flow (structured breaks/continues and returns). + When the exit leaves an exception block, the final exit statement does not have the same exception handler. + (0) + j/ \\ --> (0123) + (1) (2) | + | |j (...) + (3) (...) + + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "if_header": TemplateNode( + natural_edge=TemplateEdge( + source="if_header", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="if_header", + dest="else_body", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="if_body", + dest="tail", + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "else_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="else_body", + dest="exit_node", + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="else_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exit_node": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="exit_node", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, if_header: ControlFlowTemplate, if_body: ControlFlowTemplate, else_body: ControlFlowTemplate, exit_node: ControlFlowTemplate): + self.if_header = if_header + self.if_body = if_body + self.else_body = else_body + self.exit_node = exit_node + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ElseExitExceptTemplate._subgraph, root_key="if_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + if_else_template = ElseExitExceptTemplate(if_header=mapping["if_header"], if_body=mapping["if_body"], else_body=mapping["else_body"], exit_node=mapping["exit_node"]) + + in_edges = ((src, if_else_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [(if_else_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})] + if mapping["exception_handler"]: + out_edges.append((if_else_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([if_else_template.if_header, if_else_template.if_body, if_else_template.else_body, if_else_template.exit_node]) + reduced_cfg.add_node(if_else_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.if_header.to_indented_source(source_lines) + if_body = ControlFlowTemplate._indent_multiline_string(self.if_body.to_indented_source(source_lines)) + else_body = ControlFlowTemplate._indent_multiline_string(self.else_body.to_indented_source(source_lines)) + exit_node = ControlFlowTemplate._indent_multiline_string(self.exit_node.to_indented_source(source_lines)) + return "\n".join([header, if_body, "else: # inserted", else_body, exit_node]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ElseExitTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ElseExitTemplate.py new file mode 100644 index 0000000..4680ed2 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ElseExitTemplate.py @@ -0,0 +1,144 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree + + +class ElseExitTemplate(ControlFlowTemplate): + """ + An if-else block where only the else has no further control flow (structured breaks/continues and returns). + (0) + j/ \\ --> (012) + (1) (2) | + |j (3) + (3) + + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "if_header": TemplateNode( + natural_edge=TemplateEdge( + source="if_header", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="if_header", + dest="else_body", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="if_body", + dest="tail", + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "else_body": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="else_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, if_header: ControlFlowTemplate, if_body: ControlFlowTemplate, else_body: ControlFlowTemplate): + self.if_header = if_header + self.if_body = if_body + self.else_body = else_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ElseExitTemplate._subgraph, root_key="if_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + if_else_template = ElseExitTemplate(if_header=mapping["if_header"], if_body=mapping["if_body"], else_body=mapping["else_body"]) + + in_edges = ((src, if_else_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [(if_else_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})] + if mapping["exception_handler"]: + out_edges.append((if_else_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([if_else_template.if_header, if_else_template.if_body, if_else_template.else_body]) + reduced_cfg.add_node(if_else_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.if_header.to_indented_source(source_lines) + if_body = ControlFlowTemplate._indent_multiline_string(self.if_body.to_indented_source(source_lines)) + else_body = ControlFlowTemplate._indent_multiline_string(self.else_body.to_indented_source(source_lines)) + return "\n".join([header, if_body, "else: # inserted", else_body]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ExceptExitTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ExceptExitTemplate.py new file mode 100644 index 0000000..c6f2462 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/ExceptExitTemplate.py @@ -0,0 +1,148 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import assert_edge_type, optional_node, optional_edge, assert_in_degree + +from ..try_except.ExceptAsTemplate import ExceptAsTemplate +from ..try_except.ExceptAsExceptTemplate import ExceptAsExceptTemplate + + +class ExceptExitTemplate(ControlFlowTemplate, AbstractNonSequentiable): + """ + A `try-except` block where the except has no further control flow. + (0) + / \\e --> (012) + (1) (2) | + | (3) + (3) + """ + + _subgraph = { + "try_body": TemplateNode( + natural_edge=TemplateEdge( + source="try_body", + dest="try_footer", + ), + exception_edge=TemplateEdge( + source="try_body", + dest="except_body", + ), + ), + "try_footer": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="try_footer", dest="after_try_except", edge_verification_func=assert_edge_type(ControlFlowEdgeType.JUMP)), + exception_edge=TemplateEdge( + source="try_footer", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "after_try_except": TemplateNode( + natural_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_try_except", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, try_body: ControlFlowTemplate, try_footer: ControlFlowTemplate, except_body: ControlFlowTemplate): + self.try_body = try_body + self.try_footer = try_footer + self.except_body = except_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptExitTemplate._subgraph, root_key="try_body", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + try_except_template = ExceptExitTemplate(try_body=mapping["try_body"], try_footer=mapping["try_footer"], except_body=mapping["except_body"]) + + in_edges = ((src, try_except_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + # insert a continuation edge to after the try except + out_edges = [(try_except_template, mapping["after_try_except"], {"type": ControlFlowEdgeType.NATURAL.value})] + if mapping["outer_exception_handler"]: + out_edges.append((try_except_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([try_except_template.try_body, try_except_template.try_footer, try_except_template.except_body]) + reduced_cfg.add_node(try_except_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + try_body = ControlFlowTemplate._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + + try_except_lines = ["try:", try_body] + # if we matched against an "Except ... as" chain, then omit the inserted except: block + if isinstance(self.except_body, ExceptAsTemplate) or isinstance(self.except_body, ExceptAsExceptTemplate): + except_body = self.except_body.to_indented_source(source_lines) + else: + except_body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)) + try_except_lines.append("except:") + try_except_lines.append(except_body) + + return "\n".join(try_except_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfElseExitTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfElseExitTemplate.py new file mode 100644 index 0000000..9bb2cbe --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfElseExitTemplate.py @@ -0,0 +1,185 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import assert_edge_type, optional_node, optional_edge, assert_in_degree, node_is_none_or_matches, edge_is_none_or_matches + + +class IfElseExitTemplate(ControlFlowTemplate): + """ + An if-else block where both options have no further control flow (structured breaks/continues and returns). + (0) + j/ \\ --> (012) + (1) (2) + + optionally, all nodes in the pattern can have a shared exception handler. + nodes 1 and 2 can optionally have a "tail" that is an exit statement that breaks out of the current exception handler. + """ + + _subgraph = { + "if_header": TemplateNode( + natural_edge=TemplateEdge( + source="if_header", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="if_header", + dest="else_body", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="if_body", + dest="if_tail", + edge_verification_func=edge_is_none_or_matches(assert_edge_type(ControlFlowEdgeType.NATURAL)), + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_tail": TemplateNode( + node_verification_func=node_is_none_or_matches(assert_in_degree(1)), + exception_edge=TemplateEdge( + source="if_tail", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "else_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="else_body", + dest="else_tail", + edge_verification_func=edge_is_none_or_matches(assert_edge_type(ControlFlowEdgeType.NATURAL)), + ), + exception_edge=TemplateEdge( + source="else_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "else_tail": TemplateNode( + node_verification_func=node_is_none_or_matches(assert_in_degree(1)), + exception_edge=TemplateEdge( + source="else_tail", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__( + self, + if_header: ControlFlowTemplate, + if_body: ControlFlowTemplate, + if_tail: ControlFlowTemplate, + else_body: ControlFlowTemplate, + else_tail: ControlFlowTemplate, + ): + self.if_header = if_header + self.if_body = if_body + self.if_tail = if_tail # may be none + self.else_body = else_body + self.else_tail = else_tail # may be none + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=IfElseExitTemplate._subgraph, root_key="if_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + if_else_template = IfElseExitTemplate( + if_header=mapping["if_header"], + if_body=mapping["if_body"], + if_tail=mapping["if_tail"], + else_body=mapping["else_body"], + else_tail=mapping["else_tail"], + ) + + in_edges = ((src, if_else_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + # only preserve meta edges + out_edges = [(if_else_template, "END", data) for _, _, data in cfg.out_edges([if_else_template.if_body, if_else_template.else_body], data=True) if data["type"] == ControlFlowEdgeType.META.value] + if mapping["exception_handler"]: + out_edges.append((if_else_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([if_else_template.if_header, if_else_template.if_body, if_else_template.if_tail, if_else_template.else_body, if_else_template.else_tail]) + reduced_cfg.add_node(if_else_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.if_header.to_indented_source(source_lines) + if_body = self.if_body.to_indented_source(source_lines) + if header.strip(): + if_body = ControlFlowTemplate._indent_multiline_string(if_body) + else_body = self.else_body.to_indented_source(source_lines) + + return "\n".join([header, if_body, else_body]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfExitExceptTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfExitExceptTemplate.py new file mode 100644 index 0000000..6d3313d --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfExitExceptTemplate.py @@ -0,0 +1,162 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import get_out_edge_dict, ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree + + +class IfExitExceptTemplate(ControlFlowTemplate): + """ + An if block where the if has no further control flow (structured breaks/continues and returns). + When the exit leaves an exception block, the final exit statement does not have the same exception handler. + + (0) + j/ \\ --> (023) + (1) (2) | + | | (1) + ... (3) + + In this configuration, (0,1,2) share an exception handler, but 3 does not + """ + + _subgraph = { + "if_header": TemplateNode( + natural_edge=TemplateEdge( + source="if_header", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="if_header", + dest="tail", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + ), + ), + "if_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="if_body", + dest="exit_node", + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + ), + ), + "exit_node": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="exit_node", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + ), + ), + "exception_handler": TemplateNode( + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, if_header: ControlFlowTemplate, if_body: ControlFlowTemplate, exit_node: ControlFlowTemplate): + self.if_header = if_header + self.if_body = if_body + self.exit_node = exit_node + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + # try to match happy non-exception version + matcher = GraphTemplateMatcher(template_node_dict=IfExitExceptTemplate._subgraph, root_key="if_header", mapping_verification_func=None) + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + if_exit_template = IfExitExceptTemplate(if_header=mapping["if_header"], if_body=mapping["if_body"], exit_node=mapping["exit_node"]) + + in_edges = ((src, if_exit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + node_edge_dict = get_out_edge_dict(cfg, node) + out_edges = [(if_exit_template, node_edge_dict["conditional"][0], {"type": ControlFlowEdgeType.NATURAL.value})] + if node_edge_dict["exception"]: + out_edges.append((if_exit_template, *(node_edge_dict["exception"]))) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([if_exit_template.if_header, if_exit_template.if_body, if_exit_template.exit_node]) + reduced_cfg.add_node(if_exit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.if_header.to_indented_source(source_lines) + if_body = ControlFlowTemplate._indent_multiline_string(self.if_body.to_indented_source(source_lines)) + exit_node = ControlFlowTemplate._indent_multiline_string(self.exit_node.to_indented_source(source_lines)) + return "\n".join([header, if_body, exit_node]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfExitTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfExitTemplate.py new file mode 100644 index 0000000..d61c3cb --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/IfExitTemplate.py @@ -0,0 +1,133 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import get_out_edge_dict, ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree + + +class IfExitTemplate(ControlFlowTemplate): + """ + An if block where the if has no further control flow (structured breaks/continues and returns). + (0) + j/ \\ --> (02) + (1) (2) | + | (1) + ... + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "if_header": TemplateNode( + natural_edge=TemplateEdge( + source="if_header", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="if_header", + dest="tail", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, if_header: ControlFlowTemplate, if_body: ControlFlowTemplate): + self.if_header = if_header + self.if_body = if_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=IfExitTemplate._subgraph, root_key="if_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + if_exit_template = IfExitTemplate(if_header=mapping["if_header"], if_body=mapping["if_body"]) + + in_edges = ((src, if_exit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + node_edge_dict = get_out_edge_dict(cfg, node) + out_edges = [(if_exit_template, node_edge_dict["conditional"][0], {"type": ControlFlowEdgeType.NATURAL.value})] + if node_edge_dict["exception"]: + out_edges.append((if_exit_template, *(node_edge_dict["exception"]))) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([if_exit_template.if_header, if_exit_template.if_body]) + reduced_cfg.add_node(if_exit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.if_header.to_indented_source(source_lines) + if_body = self.if_body.to_indented_source(source_lines) + # sometimes there is no header in the case of short-circuit boolean AND + if header: + if_body = ControlFlowTemplate._indent_multiline_string(if_body) + return "\n".join([header, if_body]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/TryExitExceptExitTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/TryExitExceptExitTemplate.py new file mode 100644 index 0000000..0a86317 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/TryExitExceptExitTemplate.py @@ -0,0 +1,139 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ..try_except.ExceptAsExceptTemplate import ExceptAsExceptTemplate +from ..try_except.ExceptAsTemplate import ExceptAsTemplate +from ..try_except.ExceptAsExitTemplate import ExceptAsExitTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_instruction_opname, assert_except_as + + +class TryExitExceptExitTemplate(ControlFlowTemplate, AbstractNonSequentiable): + """ + An try block where neither the try body nor the except body has no further control flow (structured breaks/continues and returns). + (0) + | --> (012) + (1) + |e + (2) + """ + + _subgraph = { + "setup_finally": TemplateNode( + node_verification_func=assert_instruction_opname("SETUP_FINALLY"), + natural_edge=TemplateEdge( + source="setup_finally", + dest="try_body", + ), + exception_edge=TemplateEdge( + source="setup_finally", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "try_body": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="try_body", + dest="except_body", + ), + ), + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, setup_finally: ControlFlowTemplate, try_body: ControlFlowTemplate, except_body: ControlFlowTemplate): + self.setup_finally = setup_finally + self.try_body = try_body + self.except_body = except_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + # an except as exit looks exactly like this, so we need to check that we are not part of the larger pattern + def assert_not_in_except_as(cfg: nx.DiGraph, mapping: dict) -> bool: + setup_finally = mapping["setup_finally"] + if cfg.in_degree(setup_finally) != 1: + return True + + pred = next(cfg.predecessors(setup_finally)) + return not assert_except_as(cfg, pred) + + matcher = GraphTemplateMatcher(template_node_dict=TryExitExceptExitTemplate._subgraph, root_key="setup_finally", mapping_verification_func=assert_not_in_except_as) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + try_exit_template = TryExitExceptExitTemplate(setup_finally=mapping["setup_finally"], try_body=mapping["try_body"], except_body=mapping["except_body"]) + + in_edges = ((src, try_exit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((try_exit_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([try_exit_template.setup_finally, try_exit_template.try_body, try_exit_template.except_body]) + reduced_cfg.add_node(try_exit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + setup_finally = self.setup_finally.to_indented_source(source_lines) + try_body = ControlFlowTemplate._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + + try_except_lines = [setup_finally, "try:", try_body] + # if we matched against an "Except ... as" chain, then omit the inserted except: block + if isinstance(self.except_body, ExceptAsTemplate) or isinstance(self.except_body, ExceptAsExceptTemplate) or isinstance(self.except_body, ExceptAsExitTemplate): + except_body = self.except_body.to_indented_source(source_lines) + else: + except_body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)) + try_except_lines.append("except:") + try_except_lines.append(except_body) + + return "\n".join(try_except_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/TryExitTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/TryExitTemplate.py new file mode 100644 index 0000000..f73ea67 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/deprecated/TryExitTemplate.py @@ -0,0 +1,153 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractExceptionTemplate +from ..try_except.ExceptAsExceptTemplate import ExceptAsExceptTemplate +from ..try_except.ExceptAsTemplate import ExceptAsTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree + + +class TryExitTemplate(ControlFlowTemplate, AbstractExceptionTemplate): + """ + An try block where the try body has no further control flow (structured breaks/continues and returns). + (0) + e/ \\ --> (012) + (1) (2) | + | (3) + (3) + """ + + _subgraph = { + "try_body": TemplateNode( + natural_edge=TemplateEdge( + source="try_body", + dest="try_exit", + ), + exception_edge=TemplateEdge( + source="try_body", + dest="except_body", + ), + ), + "try_exit": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="try_exit", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="except_body", + dest="after_try_except", + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "after_try_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_try_except", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, try_body: ControlFlowTemplate, try_exit: ControlFlowTemplate, except_body: ControlFlowTemplate): + self.try_body = try_body + self.try_exit = try_exit + self.except_body = except_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=TryExitTemplate._subgraph, root_key="try_body", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + try_exit_template = TryExitTemplate(try_body=mapping["try_body"], try_exit=mapping["try_exit"], except_body=mapping["except_body"]) + + in_edges = ((src, try_exit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((try_exit_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + if mapping["after_try_except"]: + out_edges.append((try_exit_template, mapping["after_try_except"], {"type": ControlFlowEdgeType.NATURAL.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([try_exit_template.try_body, try_exit_template.try_exit, try_exit_template.except_body]) + reduced_cfg.add_node(try_exit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + try_body = ControlFlowTemplate._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + try_exit = ControlFlowTemplate._indent_multiline_string(self.try_exit.to_indented_source(source_lines)) + + try_except_lines = ["try:", try_body, try_exit] + # if we matched against an "Except ... as" chain, then omit the inserted except: block + if isinstance(self.except_body, ExceptAsTemplate) or isinstance(self.except_body, ExceptAsExceptTemplate): + except_body = self.except_body.to_indented_source(source_lines) + else: + except_body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)) + try_except_lines.append("except:") + try_except_lines.append(except_body) + + return "\n".join(try_except_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/if_then/ConditionalExitTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/if_then/ConditionalExitTemplate.py new file mode 100644 index 0000000..8077877 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/if_then/ConditionalExitTemplate.py @@ -0,0 +1,115 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge + + +class ConditionalExitTemplate(ControlFlowTemplate): + """ + A conditional exit within a line. Typically due to an assert statement. + (0) + j| --> (01) + (1) + + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "exit_header": TemplateNode( + conditional_edge=TemplateEdge( + source="exit_header", + dest="tail", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, exit_header: ControlFlowTemplate, tail: ControlFlowTemplate): + self.exit_header = exit_header + self.tail = tail + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ConditionalExitTemplate._subgraph, root_key="exit_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + conditional_exit_template = ConditionalExitTemplate( + exit_header=mapping["exit_header"], + tail=mapping["tail"], + ) + + in_edges = ((src, conditional_exit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = ((conditional_exit_template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(nbunch=mapping["tail"], data=True)) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([conditional_exit_template.exit_header, conditional_exit_template.tail]) + reduced_cfg.add_node(conditional_exit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.exit_header.to_indented_source(source_lines) + tail = self.tail.to_indented_source(source_lines) + return "\n".join([header, tail]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfElseTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfElseTemplate.py new file mode 100644 index 0000000..aad08e3 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfElseTemplate.py @@ -0,0 +1,194 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..natural.InstructionTemplate import InstructionTemplate + +from ..subtemplates.OptionalExitSubtemplate import ExitSubTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, node_match_all, assert_node_has_no_backwards_edges, node_match_none, assert_except_as, is_exactly_opname + +from ..natural.LinearSequenceTemplate import LinearSequenceTemplate + + +class IfElseTemplate(ControlFlowTemplate): + """ + A standard if-else-block with no extra control flow. + (0) + j/ \\ (012) + (1) (2) --> | + \\ /j (3) + (3) + + optionally, all nodes in the pattern can have a shared exception handler. + + Interestingly, this template also covers loops with guaranteed breaks and an else block. + """ + + _subgraph = { + "if_header": TemplateNode( + node_verification_func=node_match_none(assert_except_as, is_exactly_opname("CLEANUP_THROW", "END_SEND", "POP_JUMP_IF_TRUE")), + natural_edge=TemplateEdge( + source="if_header", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="if_header", + dest="else_body", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + ), + natural_edge=TemplateEdge( + source="if_body", + dest="tail", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "else_body": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + ), + natural_edge=TemplateEdge( + source="else_body", + dest="tail", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="else_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + commit_none_to_mapping=False, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + commit_none_to_mapping=False, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + commit_none_to_mapping=False, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + commit_none_to_mapping=False, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + commit_none_to_mapping=False, + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + commit_none_to_mapping=False, + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, if_header: ControlFlowTemplate, if_body: ControlFlowTemplate, else_body: ControlFlowTemplate): + self.if_header = if_header + self.if_body = if_body + self.else_body = else_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=IfElseTemplate._subgraph, root_key="if_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + if_else_template = IfElseTemplate(if_header=mapping["if_header"], if_body=mapping["if_body"], else_body=mapping["else_body"]) + + in_edges = ((src, if_else_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + if "tail" in mapping: + out_edges = [(if_else_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})] + else: + out_edges = [] + if mapping["exception_handler"]: + out_edges.append((if_else_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([if_else_template.if_header, if_else_template.if_body, if_else_template.else_body]) + reduced_cfg.add_node(if_else_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + if_lines = [] + header = self.if_header.to_indented_source(source_lines).rstrip() + if header and header.split("\n")[-1].strip().startswith("assert "): + return "\n".join([header, self.if_body.to_indented_source(source_lines), self.else_body.to_indented_source(source_lines)]) + if header: + if_lines.append(header) + if_body = ControlFlowTemplate._indent_multiline_string(self.if_body.to_indented_source(source_lines)) + if if_body: + if_lines.append(if_body) + else_body = ControlFlowTemplate._indent_multiline_string(self.else_body.to_indented_source(source_lines)) + if else_body: + if_lines.extend(["else: # inserted", else_body]) + + # edge case hack to deal with for loops that have guaranteed breaks (they look exactly like if statements) + # while loops should be translated as if statements in this case, so we don't have to worry there + if isinstance(self.if_header, LinearSequenceTemplate): + last_member = self.if_header.members[-1] + if isinstance(last_member, InstructionTemplate) and last_member.instruction.opname == "FOR_ITER": + if_lines.insert(2, "\tbreak # inserted") + + return "\n".join(if_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfThenJumpTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfThenJumpTemplate.py new file mode 100644 index 0000000..cefd54d --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfThenJumpTemplate.py @@ -0,0 +1,162 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..natural.InstructionTemplate import InstructionTemplate +from ..natural.LinearSequenceTemplate import LinearSequenceTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_unconditional_jump + + +class IfThenJumpTemplate(ControlFlowTemplate): + """ + A standard if-block with no extra control flow. + This variant has an absolute jump from the end of the if body to the outside. + This occurs when there are nested if-else blocks and the inner if statements jump out directly to the top level. + (0) + | \\ (01) + j| (1) --> | + | | (2) + | (2) |j + | /j (3) + (3) + + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "if_header": TemplateNode( + natural_edge=TemplateEdge( + source="if_header", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="if_header", + dest="tail", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="if_body", + dest="jump", + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "jump": TemplateNode( + node_verification_func=assert_unconditional_jump, + natural_edge=TemplateEdge( + source="jump", + dest="tail", + ), + exception_edge=TemplateEdge( + source="jump", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, if_header: ControlFlowTemplate, if_body: ControlFlowTemplate): + self.if_header = if_header + self.if_body = if_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=IfThenJumpTemplate._subgraph, root_key="if_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + if_then_template = IfThenJumpTemplate(if_header=mapping["if_header"], if_body=mapping["if_body"]) + + in_edges = ((src, if_then_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [(if_then_template, mapping["jump"], {"type": ControlFlowEdgeType.NATURAL.value})] + if mapping["exception_handler"]: + out_edges.append((if_then_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([if_then_template.if_header, if_then_template.if_body]) + reduced_cfg.add_node(if_then_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.if_header.to_indented_source(source_lines).strip() + body = ControlFlowTemplate._indent_multiline_string(self.if_body.to_indented_source(source_lines)) + + if_lines = [header, body] + + # edge case hack to deal with for loops that have guaranteed breaks (they look exactly like if statements) + # while loops should be translated as if statements in this case, so we don't have to worry there + if isinstance(self.if_header, LinearSequenceTemplate): + last_member = self.if_header.members[-1] + if isinstance(last_member, InstructionTemplate) and last_member.instruction.opname == "FOR_ITER": + if_lines.insert(2, "\tbreak # inserted") + + return "\n".join(if_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfThenTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfThenTemplate.py new file mode 100644 index 0000000..41c7d0e --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/if_then/IfThenTemplate.py @@ -0,0 +1,173 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..natural.InstructionTemplate import InstructionTemplate +from ..natural.LinearSequenceTemplate import LinearSequenceTemplate + +from ..subtemplates.OptionalExitSubtemplate import ExitSubTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_node_has_no_backwards_edges, node_match_all, assert_except_as, node_match_none + + +class IfThenTemplate(ControlFlowTemplate): + """ + A standard if-block with no extra control flow. + (0) + | \\ (01) + j| (1) --> | + | / (2) + (2) + + optionally, all nodes in the pattern can have a shared exception handler. + + Interestingly, this template also covers loops with guaranteed breaks. + """ + + _subgraph = { + "if_header": TemplateNode( + node_verification_func=node_match_none(assert_except_as), + natural_edge=TemplateEdge( + source="if_header", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="if_header", + dest="tail", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + ), + natural_edge=TemplateEdge( + source="if_body", + dest="tail", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest=None, + commit_none_to_mapping=False, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, if_header: ControlFlowTemplate, if_body: ControlFlowTemplate): + self.if_header = if_header + self.if_body = if_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=IfThenTemplate._subgraph, root_key="if_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + if_then_template = IfThenTemplate(if_header=mapping["if_header"], if_body=mapping["if_body"]) + + in_edges = ((src, if_then_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [(if_then_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})] + if mapping["exception_handler"]: + out_edges.append((if_then_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([if_then_template.if_header, if_then_template.if_body]) + reduced_cfg.add_node(if_then_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.if_header.to_indented_source(source_lines).strip() + """ + if header.startswith('while ') and isinstance(self.if_body, RefinedLoopTemplate) and isinstance(self.if_body.loop_header, WhileTruePlaceholderTemplate): + if isinstance(self.if_body.loop_body, LinearSequenceTemplate): + last = self.if_body.loop_body.members[-1] + else: + last = self.if_body.loop_body + assert isinstance(last, IfElseTemplate) + last.to_indented_source = last.if_header.to_indented_source + self.if_body.loop_header.to_indented_source = lambda x: '' + if isinstance(self.if_body, LoopExitTemplate) and not header.startswith('if '): + body = '' + else: + body = ControlFlowTemplate._indent_multiline_string(self.if_body.to_indented_source(source_lines)) + """ + body = ControlFlowTemplate._indent_multiline_string(self.if_body.to_indented_source(source_lines)) + + if_lines = [header, body] + + # edge case hack to deal with for loops that have guaranteed breaks (they look exactly like if statements) + # while loops should be translated as if statements in this case, so we don't have to worry there + if isinstance(self.if_header, LinearSequenceTemplate): + last_member = self.if_header.members[-1] + if isinstance(last_member, InstructionTemplate) and last_member.instruction.opname == "FOR_ITER": + if_lines.insert(2, "\tbreak # inserted") + + return "\n".join(if_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/loop/AsyncForTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/loop/AsyncForTemplate.py new file mode 100644 index 0000000..1f809e5 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/loop/AsyncForTemplate.py @@ -0,0 +1,152 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_instruction_opname, node_match_all, assert_first_instruction_opname + + +class AsyncForTemplate(ControlFlowTemplate): + """ + An async for loop. + (-1) + | ^ + (0) |j + | \\| (-101) + e| (1) --> | + | (2) + (2) + + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "loop_header": TemplateNode( + node_verification_func=assert_instruction_opname("SETUP_FINALLY"), + natural_edge=TemplateEdge( + source="loop_header", + dest="loop_iter", + ), + exception_edge=TemplateEdge( + source="loop_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "loop_iter": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1), assert_first_instruction_opname("GET_ANEXT")), + natural_edge=TemplateEdge( + source="loop_iter", + dest="loop_body", + ), + exception_edge=TemplateEdge( + source="loop_iter", + dest="tail", + ), + ), + "loop_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="loop_body", + dest="loop_header", + ), + exception_edge=TemplateEdge( + source="loop_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, loop_header: ControlFlowTemplate, loop_iter: ControlFlowTemplate, loop_body: ControlFlowTemplate): + self.loop_header = loop_header + self.loop_iter = loop_iter + self.loop_body = loop_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + if cfg.in_degree(node) != 1: + return None + + # to avoid being treated as a try-except, we actually need to greedily search up one layer + pred = next(cfg.predecessors(node)) + + matcher = GraphTemplateMatcher(template_node_dict=AsyncForTemplate._subgraph, root_key="loop_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, pred) + + if not mapping: + return None + + loop_template = AsyncForTemplate(loop_header=mapping["loop_header"], loop_iter=mapping["loop_iter"], loop_body=mapping["loop_body"]) + + in_edges = ((src, loop_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=pred, data=True) if src != mapping["loop_body"]) + out_edges = [(loop_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})] + if mapping["exception_handler"]: + out_edges.append((loop_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([loop_template.loop_header, loop_template.loop_iter, loop_template.loop_body]) + reduced_cfg.add_node(loop_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.loop_header.to_indented_source(source_lines) + loop_iter = self.loop_iter.to_indented_source(source_lines) + body = ControlFlowTemplate._indent_multiline_string(self.loop_body.to_indented_source(source_lines)) + return "\n".join([header, loop_iter, body]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/loop/ForIf312Template.py b/pylingual/control_flow_reconstruction/control_flow_templates/loop/ForIf312Template.py new file mode 100644 index 0000000..5fcf0c8 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/loop/ForIf312Template.py @@ -0,0 +1,120 @@ +import networkx as nx + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher +from ..natural.InstructionTemplate import InstructionTemplate +from .LoopExitTemplate import LoopExitTemplate + +from ..match_utils import optional_node, optional_edge, assert_in_degree, node_match_all +from ...cfg_utils import ControlFlowEdgeType + + +def is_j(cfg: nx.DiGraph, node) -> bool: + return isinstance(node, LoopExitTemplate) and isinstance(node.tail, InstructionTemplate) and node.tail.instruction.opname == "JUMP_BACKWARD" and node.exit_statement == "continue" and node.tail.instruction.target.opname == "FOR_ITER" + + +class ForIf312Template(ControlFlowTemplate): + _subgraph = { + "if_header": TemplateNode( + natural_edge=TemplateEdge( + source="if_header", + dest="jump_back", + ), + conditional_edge=TemplateEdge( + source="if_header", + dest="real_body", + ), + exception_edge=TemplateEdge( + source="if_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "jump_back": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1), is_j), + exception_edge=TemplateEdge( + source="jump_back", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "real_body": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1)), + exception_edge=TemplateEdge( + source="real_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, if_header: ControlFlowTemplate, body: ControlFlowTemplate, jb: ControlFlowTemplate): + self.if_header = if_header + self.body = body + self.jb = jb + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ForIf312Template._subgraph, root_key="if_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + template = ForIf312Template(if_header=mapping["if_header"], body=mapping["real_body"], jb=mapping["jump_back"]) + + in_edges = ((src, template, edge) for src, dst, edge in cfg.in_edges(node, data=True)) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([mapping["if_header"], mapping["real_body"], mapping["jump_back"]]) + reduced_cfg.add_node(template) + reduced_cfg.add_edges_from(in_edges) + if mapping["exception_handler"]: + reduced_cfg.add_edge(template, mapping["exception_handler"], type=ControlFlowEdgeType.EXCEPTION.value) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + header = self.if_header.to_indented_source(source_lines) + body = self._indent_multiline_string(self.body.to_indented_source(source_lines)) + """ + n = header.strip().split('\n')[-1].strip().startswith('if not ') + fj = self.if_header.get_instructions()[-1].opname == 'POP_JUMP_IF_FALSE' + breakpoint() + if fj != n: + header += '\n\tpass\nelse: # inserted' + """ + last = max((i.starts_line for i in self.if_header.get_instructions() if i.starts_line is not None), default=None) + if last is not None and last < len(source_lines) and body.split("\n")[0].strip() != source_lines[last].strip(): + header += "\n\tpass\nelse: # inserted" + return header + "\n" + body + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/loop/InlinedComprehension.py b/pylingual/control_flow_reconstruction/control_flow_templates/loop/InlinedComprehension.py new file mode 100644 index 0000000..a7e2b79 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/loop/InlinedComprehension.py @@ -0,0 +1,97 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, node_match_all + + +def is_cleanup(cfg: nx.DiGraph, node: ControlFlowTemplate) -> bool: + insts = node.get_instructions() + if not insts or insts[-1].opname != "RERAISE": + return False + if [i.opname for i in insts[:3]] != ["SWAP", "POP_TOP", "SWAP"]: + return False + return all(i.opname == "STORE_FAST" for i in insts[3:-1]) + + +class InlinedComprehensionTemplate(ControlFlowTemplate): + _subgraph = { + "comp": TemplateNode( + natural_edge=TemplateEdge( + source="comp", + dest="tail", + ), + exception_edge=TemplateEdge( + source="comp", + dest="cleanup", + ), + ), + "cleanup": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1), is_cleanup), + exception_edge=TemplateEdge( + source="cleanup", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge(source="tail", dest=None, edge_verification_func=optional_edge), + conditional_edge=TemplateEdge(source="tail", dest=None, edge_verification_func=optional_edge), + exception_edge=TemplateEdge(source="tail", dest="exception_handler", edge_verification_func=optional_edge), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge(source="exception_handler", dest=None, edge_verification_func=optional_edge), + conditional_edge=TemplateEdge(source="exception_handler", dest=None, edge_verification_func=optional_edge), + exception_edge=TemplateEdge(source="exception_handler", dest=None, edge_verification_func=optional_edge), + ), + } + + def __init__(self, comp: ControlFlowTemplate, cleanup: ControlFlowTemplate): + self.comp = comp + self.cleanup = cleanup + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=InlinedComprehensionTemplate._subgraph, root_key="comp", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + template = InlinedComprehensionTemplate(comp=mapping["comp"], cleanup=mapping["cleanup"]) + + in_edges = ((src, template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [(template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})] + if mapping["exception_handler"]: + out_edges.append((template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([template.comp, template.cleanup]) + reduced_cfg.add_node(template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + return "" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/loop/LoopExitTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/loop/LoopExitTemplate.py new file mode 100644 index 0000000..cb684c4 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/loop/LoopExitTemplate.py @@ -0,0 +1,54 @@ +import networkx as nx + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..try_except.ExceptAsTemplate import ExceptAsTemplate + +from ...cfg_utils import get_out_edge_dict, ControlFlowEdgeType + + +class LoopExitTemplate(ControlFlowTemplate): + """ + A wrapper for identified break and continue statements. + """ + + def __init__(self, exit_statement: str, tail: ControlFlowTemplate = None): + self.tail = tail + self.exit_statement = exit_statement + assert self.exit_statement in ["break", "continue"] + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + raise NotImplementedError("Loop Exits do not have localized matching logic. These are assigned in refine_loops.") + + @staticmethod + def structure_edge_inplace(cfg: nx.DiGraph, edge: tuple, exit_statment: str) -> None: + src, dst = edge + edge_properties = cfg.get_edge_data(src, dst) + + cfg.remove_edge(src, dst) + # for an unconditional jump, integrate the tail into the exit template + if edge_properties.get("type", None) == ControlFlowEdgeType.JUMP.value: + template = LoopExitTemplate(exit_statement=exit_statment, tail=src) + nx.relabel_nodes(cfg, {src: template}, copy=False) + else: + template = LoopExitTemplate(exit_statement=exit_statment) + cfg.add_edge(src, template, **edge_properties) + src_exception_handler = get_out_edge_dict(cfg, src).get("exception") + if src_exception_handler != (None, None): + cfg.add_edge(template, src_exception_handler[0], type=ControlFlowEdgeType.EXCEPTION.value) + return template + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + tail_source = self.tail.to_indented_source(source_lines) + "\n" if self.tail else "" + + exit_statement = self.exit_statement + if isinstance(self.tail, ExceptAsTemplate): + exit_statement = ControlFlowTemplate._indent_multiline_string(self.exit_statement) + + return tail_source + exit_statement + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/loop/LoopTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/loop/LoopTemplate.py new file mode 100644 index 0000000..446cd8f --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/loop/LoopTemplate.py @@ -0,0 +1,143 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import ControlFlowEdgeType, get_dominator_function + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree + + +class LoopTemplate(ControlFlowTemplate): + """ + A natural non-infinite loop with no extra control flow. + (0) + | \\ (01) + j| (1) --> | + | (2) + (2) + + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "loop_header": TemplateNode( + natural_edge=TemplateEdge( + source="loop_header", + dest="loop_body", + ), + conditional_edge=TemplateEdge( + source="loop_header", + dest="tail", + ), + exception_edge=TemplateEdge( + source="loop_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "loop_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="loop_body", + dest="loop_header", + ), + exception_edge=TemplateEdge( + source="loop_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, loop_header: ControlFlowTemplate, loop_body: ControlFlowTemplate): + self.loop_header = loop_header + self.loop_body = loop_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + def verify_tail_not_in_loop(cfg: nx.DiGraph, mapping: dict) -> bool: + dominates = get_dominator_function(cfg) + # subgraph containing all nodes dominated by the loop header + dominated_subgraph: nx.DiGraph = cfg.subgraph(n for n in cfg.nodes if dominates(mapping["loop_header"], n)) + reverse_reachability_map = nx.single_source_shortest_path_length(dominated_subgraph.reverse(), source=mapping["loop_header"]) + # a node is in the loop if there is a backwards path to the header that doesn't leave the loop + loop_nodes = [loop_node for loop_node, distance in reverse_reachability_map.items() if distance >= 0] + return mapping["tail"] not in loop_nodes + + matcher = GraphTemplateMatcher(template_node_dict=LoopTemplate._subgraph, root_key="loop_header", mapping_verification_func=verify_tail_not_in_loop) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + loop_template = LoopTemplate(loop_header=mapping["loop_header"], loop_body=mapping["loop_body"]) + + in_edges = ((src, loop_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True) if src != mapping["loop_body"]) + out_edges = [(loop_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})] + if mapping["exception_handler"]: + out_edges.append((loop_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([loop_template.loop_header, loop_template.loop_body]) + reduced_cfg.add_node(loop_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.loop_header.to_indented_source(source_lines) + body = ControlFlowTemplate._indent_multiline_string(self.loop_body.to_indented_source(source_lines)) + return "\n".join([header, body]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/loop/PreRefinedLoopTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/loop/PreRefinedLoopTemplate.py new file mode 100644 index 0000000..073d81c --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/loop/PreRefinedLoopTemplate.py @@ -0,0 +1,57 @@ +import networkx as nx + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import get_out_edge_dict + +from ..placeholders.WhileTruePlaceholderTemplate import WhileTruePlaceholderTemplate + + +class PreRefinedLoopTemplate(ControlFlowTemplate): + """ + Matches a loop header for an unrefined loop containing breaks and continues. + Results in a RefinedLoopTemplate header and replaces all breaks and continues with LoopExitTemplates + """ + + def __init__(self, loop_header: ControlFlowTemplate, loop_else: ControlFlowTemplate): + self.loop_header = loop_header + self.loop_else = loop_else + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + raise NotImplementedError("PreRefinedLoopTemplate does not have local matching logic. These are created in refine_loop") + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + return self.loop_header.to_indented_source(source_lines) + + @staticmethod + def structure_nodes_inplace(cfg: nx.DiGraph, loop_header, canonical_loop_exit, loop_successor): + if not canonical_loop_exit: + # while true; use a placeholder that makes the while true "look like" a normal loop + loop_header = WhileTruePlaceholderTemplate.structure_node_inplace(cfg, loop_header, loop_successor) + loop_template = PreRefinedLoopTemplate(loop_header=loop_header, loop_else=None) + if canonical_loop_exit != loop_successor: + loop_template = PreRefinedLoopTemplate(loop_header=loop_header, loop_else=canonical_loop_exit) + else: + loop_template = PreRefinedLoopTemplate(loop_header=loop_header, loop_else=None) + + in_edges = ((src, loop_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=loop_header, data=True)) + out_edges = [(loop_template, loop_successor if dst == canonical_loop_exit else dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(nbunch=loop_header, data=True)] + + loop_header_out_dict = get_out_edge_dict(cfg, loop_header) + exception_target, edge_type = loop_header_out_dict["exception"] + if exception_target: + out_edges.append((loop_template, exception_target, edge_type)) + + cfg.remove_node(loop_template.loop_header) + if loop_template.loop_else: + cfg.remove_node(loop_template.loop_else) + cfg.add_node(loop_template) + cfg.add_edges_from(in_edges) + cfg.add_edges_from(out_edges) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/loop/RefinedLoopTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/loop/RefinedLoopTemplate.py new file mode 100644 index 0000000..cd8bb77 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/loop/RefinedLoopTemplate.py @@ -0,0 +1,147 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from .PreRefinedLoopTemplate import PreRefinedLoopTemplate +from ..placeholders.WhileTruePlaceholderTemplate import WhileTruePlaceholderTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree + + +class RefinedLoopTemplate(ControlFlowTemplate): + """ + The second stage of matching loops with breaks an continues; matches fully-structured PreRefinedLoopTemplates. + (0) = PreRefinedLoopTemplate + // \\j --> (01) + (1) (2) | + (2) + + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "pre_refined_loop": TemplateNode( + natural_edge=TemplateEdge( + source="pre_refined_loop", + dest="loop_body", + ), + conditional_edge=TemplateEdge(source="pre_refined_loop", dest="loop_successor", edge_verification_func=optional_edge), + exception_edge=TemplateEdge( + source="pre_refined_loop", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "loop_body": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="loop_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "loop_successor": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="loop_successor", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="loop_successor", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="loop_successor", + dest="exception_handler", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, loop_header: ControlFlowTemplate, loop_body: ControlFlowTemplate, loop_else: ControlFlowTemplate, has_successor: bool = True): + self.loop_header = loop_header + self.loop_body = loop_body + self.loop_else = loop_else + self.has_successor = has_successor + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + # this pattern is only for matching on PreRefinedLoops + if not isinstance(node, PreRefinedLoopTemplate): + return None + + matcher = GraphTemplateMatcher(template_node_dict=RefinedLoopTemplate._subgraph, root_key="pre_refined_loop", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + loop_template = RefinedLoopTemplate(loop_header=mapping["pre_refined_loop"].loop_header, loop_body=mapping["loop_body"], loop_else=mapping["pre_refined_loop"].loop_else, has_successor=bool(mapping["loop_successor"])) + + in_edges = ((src, loop_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [] + if mapping["loop_successor"]: + out_edges.append((loop_template, mapping["loop_successor"], {"type": ControlFlowEdgeType.NATURAL.value})) + if mapping["exception_handler"]: + out_edges.append((loop_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([mapping["pre_refined_loop"], loop_template.loop_body]) + reduced_cfg.add_node(loop_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + loop_lines = [] + header = self.loop_header.to_indented_source(source_lines) + if not self.has_successor and not isinstance(self.loop_header, WhileTruePlaceholderTemplate): + header = ControlFlowTemplate._indent_multiline_string(header) + loop_lines.append("while True: # inserted") + loop_body = ControlFlowTemplate._indent_multiline_string(self.loop_body.to_indented_source(source_lines)) + loop_lines.extend([header, loop_body]) + if self.loop_else: + loop_else = ControlFlowTemplate._indent_multiline_string(self.loop_else.to_indented_source(source_lines)) + loop_lines.extend(["else: # inserted", loop_else]) + + return "\n".join(loop_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/loop/SelfLoopTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/loop/SelfLoopTemplate.py new file mode 100644 index 0000000..18ef05b --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/loop/SelfLoopTemplate.py @@ -0,0 +1,85 @@ +import networkx as nx + + +from ..abstract.AbstractTemplate import ControlFlowTemplate + + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge + + +class SelfLoopTemplate(ControlFlowTemplate): + """ + An infinite loop with no extra control flow. + (0)-< --> (0) + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "loop_body": TemplateNode( + natural_edge=TemplateEdge( + source="loop_body", + dest="loop_body", + ), + exception_edge=TemplateEdge( + source="loop_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, loop_body: ControlFlowTemplate): + self.loop_body = loop_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=SelfLoopTemplate._subgraph, root_key="loop_body", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + loop_template = SelfLoopTemplate(loop_body=mapping["loop_body"]) + + reduced_cfg: nx.DiGraph = nx.relabel_nodes(cfg, {mapping["loop_body"]: loop_template}) + reduced_cfg.remove_edge(loop_template, loop_template) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + body = ControlFlowTemplate._indent_multiline_string(self.loop_body.to_indented_source(source_lines)) + return f"while True: # inserted\n{body}" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/loop/WhileTrueIfElseTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/loop/WhileTrueIfElseTemplate.py new file mode 100644 index 0000000..cc4bcb8 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/loop/WhileTrueIfElseTemplate.py @@ -0,0 +1,135 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..if_then.IfElseTemplate import IfElseTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree + + +class WhileTrueIfElseTemplate(ControlFlowTemplate): + """ + A while true that contains in if-else statement at the top level. + (0) + j| \\ + (2) (1) --> (012) + + nodes 1 and 2 have a backwards unconditional jump to 0 + optionally, all nodes in the pattern can have a shared exception handler. + """ + + _subgraph = { + "loop_header": TemplateNode( + natural_edge=TemplateEdge( + source="loop_header", + dest="if_body", + ), + conditional_edge=TemplateEdge( + source="loop_header", + dest="else_body", + ), + exception_edge=TemplateEdge( + source="loop_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "if_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="if_body", + dest="loop_header", + ), + exception_edge=TemplateEdge( + source="if_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "else_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="else_body", + dest="loop_header", + ), + exception_edge=TemplateEdge( + source="else_body", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, loop_header: ControlFlowTemplate, if_body: ControlFlowTemplate, else_body: ControlFlowTemplate): + self.loop_header = loop_header + self.if_body = if_body + self.else_body = else_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=WhileTrueIfElseTemplate._subgraph, root_key="loop_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + loop_template = WhileTrueIfElseTemplate( + loop_header=mapping["loop_header"], + if_body=mapping["if_body"], + else_body=mapping["else_body"], + ) + + in_edges = ((src, loop_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True) if src != mapping["if_body"] and src != mapping["else_body"]) + out_edges = [] + if mapping["exception_handler"]: + out_edges.append((loop_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([loop_template.loop_header, loop_template.if_body, loop_template.else_body]) + reduced_cfg.add_node(loop_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + + if_else_template = IfElseTemplate(if_header=self.loop_header, if_body=self.if_body, else_body=self.else_body) + body = ControlFlowTemplate._indent_multiline_string(if_else_template.to_indented_source(source_lines)) + return "\n".join(["while True: # inserted", body]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/match_utils.py b/pylingual/control_flow_reconstruction/control_flow_templates/match_utils.py new file mode 100644 index 0000000..bc06f7f --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/match_utils.py @@ -0,0 +1,232 @@ +import networkx as nx + +import itertools +import collections + +from ..cfg_utils import ControlFlowEdgeType, get_dominator_function +from .natural.InstructionTemplate import InstructionTemplate + +from .abstract.AbstractTemplate import ControlFlowTemplate +from .natural.LinearSequenceTemplate import LinearSequenceTemplate + +from typing import Callable, Any + +# common node/edge/mapping verification functions and factories + + +def assert_edge_type(*edge_types: ControlFlowEdgeType) -> Callable[[Any, Any, dict], bool]: + def initialized_assert_edge_type(graph_source, graph_dest, graph_edge_properties: dict) -> bool: + if graph_edge_properties is None: + return False + return graph_edge_properties.get("type", None) in [edge_type.value for edge_type in edge_types] + + return initialized_assert_edge_type + + +def assert_node_type(*node_types: type) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_assert_node_type(cfg: nx.DiGraph, node) -> bool: + return any(isinstance(node, node_type) for node_type in node_types) + + return initialized_assert_node_type + + +def assert_in_degree(in_degree: int) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_assert_in_degree(cfg: nx.DiGraph, node) -> bool: + if node is None: + return False + return cfg.in_degree(node) == in_degree + + return initialized_assert_in_degree + + +def assert_instruction_opname(*opnames: str) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_assert_instruction_opname(cfg: nx.DiGraph, node) -> bool: + if node is None: + return False + + if isinstance(node, LinearSequenceTemplate): + candidate = node.members[-1] + else: + candidate = node + + if not isinstance(candidate, InstructionTemplate): + return False + return candidate.instruction.opname in opnames + + return initialized_assert_instruction_opname + + +def assert_first_instruction_opname(*opnames: str) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_assert_first_instruction_opname(cfg: nx.DiGraph, node) -> bool: + """ + if node is None: + return False + + if isinstance(node, LinearSequenceTemplate): + candidate = node.members[0] + else: + candidate = node + + if not isinstance(candidate, InstructionTemplate): + return False + return candidate.instruction.opname in opnames + """ + i = node.get_instructions() + return i and i[0].opname in opnames + + return initialized_assert_first_instruction_opname + + +def assert_unconditional_jump(cfg: nx.DiGraph, node) -> bool: + if not isinstance(node, InstructionTemplate): + return False + return node.instruction.is_uncond_jump + + +def optional_node(cfg, node) -> bool: + # returns true even when node is None! + # overrides default behavior of checking if the node exists + return True + + +def optional_edge(graph_source, graph_dest, graph_edge_properties: dict) -> bool: + # returns true even when the edge is None! + # overrides default behavior of checking if the edge exists + return True + + +def edge_is_none_or_matches(verification_func: Callable[[nx.DiGraph, Any], bool]) -> Callable[[Any, Any, dict], bool]: + def initialized_edge_is_none_or_matches(graph_source, graph_dest, graph_edge_properties: dict) -> bool: + return graph_dest is None or verification_func(graph_source, graph_dest, graph_edge_properties) + + return initialized_edge_is_none_or_matches + + +def node_is_none_or_matches(verification_func: Callable[[nx.DiGraph, Any], bool]) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_node_is_none_or_matches(cfg: nx.DiGraph, node) -> bool: + return node is None or verification_func(cfg, node) + + return initialized_node_is_none_or_matches + + +def node_match_all(*verification_funcs: Callable[[nx.DiGraph, Any], bool]) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_match_all(cfg: nx.DiGraph, node) -> bool: + return all(f(cfg, node) for f in verification_funcs) + + return initialized_match_all + + +def node_match_none(*verification_funcs: Callable[[nx.DiGraph, Any], bool]) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_match_all(cfg: nx.DiGraph, node) -> bool: + return not any(f(cfg, node) for f in verification_funcs) + + return initialized_match_all + + +def node_match_any(*verification_funcs: Callable[[nx.DiGraph, Any], bool]) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_match_any(cfg: nx.DiGraph, node) -> bool: + return any(f(cfg, node) for f in verification_funcs) + + return initialized_match_any + + +def assert_no_linestarts(cfg: nx.DiGraph, node: ControlFlowTemplate) -> bool: + return not any(inst.starts_line for inst in node.get_instructions()) + + +def contains_opname_sequence(*opnames: str) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_contains_opname_sequence(cfg: nx.DiGraph, node: ControlFlowTemplate) -> bool: + for window in sliding_window(node.get_instructions(), n=len(opnames)): + if tuple(inst.opname for inst in window) == opnames: + return True + return False + + return initialized_contains_opname_sequence + + +def starts_with_opname_sequence(*opnames: str) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_starts_with_opname_sequence(cfg: nx.DiGraph, node: ControlFlowTemplate) -> bool: + i = node.get_instructions() + return len(i) >= len(opnames) and tuple(x.opname for x in i[: len(opnames)]) == opnames + + return initialized_starts_with_opname_sequence + + +def ends_with_opname_sequence(*opnames: str) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_ends_with_opname_sequence(cfg: nx.DiGraph, node: ControlFlowTemplate) -> bool: + i = node.get_instructions() + return len(i) >= len(opnames) and tuple(x.opname for x in i[-len(opnames) :]) == opnames + + return initialized_ends_with_opname_sequence + + +def is_exactly_opname(*opnames: str) -> Callable[[nx.DiGraph, Any], bool]: + def initialized_is_exactly_opname(cfg: nx.DiGraph, node: ControlFlowTemplate) -> bool: + return isinstance(node, ControlFlowTemplate) and tuple(x.opname for x in node.get_instructions()) == opnames + + return initialized_is_exactly_opname + + +def assert_node_has_no_backwards_edges(cfg, node) -> bool: + dominates = get_dominator_function(cfg) + return not any(dominates(successor, node) for successor in cfg.successors(node)) + + +def assert_except_as(cfg: nx.DiGraph, node: ControlFlowTemplate) -> bool: + # specialized node verification function for the header + # the header must be a LinearSequence where the last instruction is JUMP_IF_NOT_EXC_MATCH + # this instruction is used *exclusively* for except-as constructions in pre-3.11 bytecode + # this rule only applies to versions 3.9 and 3.10 + if not isinstance(node, LinearSequenceTemplate): + return False + + # version 3.9-3.10 + exc_match_member = node.members[-1] + if not isinstance(exc_match_member, InstructionTemplate): + return False + if exc_match_member.instruction.opname == "JUMP_IF_NOT_EXC_MATCH": + return True + + # so we dont throw errors + + if len(node.members) < 2: + return False + + # pre-3.9 + exc_match_member = node.members[-2] + if not isinstance(exc_match_member, InstructionTemplate): + return False + if exc_match_member.instruction.opname == "COMPARE_OP" and exc_match_member.instruction.argval == "exception-match": + return True + + # 3.11 + if exc_match_member.instruction.opname == "CHECK_EXC_MATCH": + return True + return False + + +def assert_with(cfg: nx.DiGraph, node: ControlFlowTemplate) -> bool: + # these statements begin in a linear sequence template + # so if the node is not in a linear sequence template then this is not + # a with statement + + if not isinstance(node, LinearSequenceTemplate): + return False + + # designed for version 3.8 might be different for other versions + with_match_member = node.members[-1] # get the last element that should be a SETUP_WITH + if not isinstance(with_match_member, InstructionTemplate): + return False + if with_match_member.instruction.opname in ("SETUP_WITH", "SETUP_ASYNC_WITH", "BEFORE_WITH", "END_SEND"): + return True + + +# iteration helper +def sliding_window(iterable, n): + "Collect data into overlapping fixed-length chunks or blocks." + # sliding_window('ABCDEFG', 4) → ABCD BCDE CDEF DEFG + it = iter(iterable) + window = collections.deque(itertools.islice(it, n - 1), maxlen=n) + for x in it: + window.append(x) + yield tuple(window) diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/natural/InstructionTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/natural/InstructionTemplate.py new file mode 100644 index 0000000..d5d636b --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/natural/InstructionTemplate.py @@ -0,0 +1,69 @@ +import networkx as nx + +from pylingual.editable_bytecode import Inst + +from ..abstract.AbstractTemplate import ControlFlowTemplate + + +class InstructionTemplate(ControlFlowTemplate): + """ + A thin wrapper around the Inst class to support formatting source code + """ + + def __init__(self, instruction: Inst): + self.instruction = instruction + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if not isinstance(node, Inst): + return None + + if node not in cfg.nodes: + return None + + inst_template = InstructionTemplate(node) + return nx.relabel_nodes(cfg, mapping={node: inst_template}, copy=True) + + @staticmethod + def match_graph(cfg: nx.DiGraph) -> nx.DiGraph: + """ + Attempts to match this template on the whole graph + Returns an updated cfg with the appropriate nodes condensed into an instance of this template. + """ + node_mapping = dict() + for node in cfg.nodes: + if not isinstance(node, Inst): + continue + + inst_template = InstructionTemplate(node) + node_mapping[node] = inst_template + + return nx.relabel_nodes(cfg, mapping=node_mapping, copy=True) + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + if not self.instruction.starts_line: + return "" + + line = source_lines[self.instruction.starts_line - 1].strip() + if line.startswith("elif "): + line = line[2:] + elif line in ("break", "continue", "except:", "try:"): + line = "" + + return line + + def get_instructions(self) -> list[Inst]: + return [self.instruction] + + def __repr__(self) -> str: + if self.instruction.starts_line: + return f"({self.instruction.starts_line}) <{self.instruction.get_dis_view()}>" + return f"<{self.instruction.get_dis_view()}>" diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/natural/LineTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/natural/LineTemplate.py new file mode 100644 index 0000000..c1000a7 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/natural/LineTemplate.py @@ -0,0 +1,37 @@ +import networkx as nx + +from pylingual.editable_bytecode import Inst + +from ..abstract.AbstractTemplate import ControlFlowTemplate + + +class LineTemplate(ControlFlowTemplate): + """ + A natural progression of control flow templates with the same exception handler. + No conditional jumps are allowed. + """ + + def __init__(self, *members: ControlFlowTemplate): + self.members = members + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + raise NotImplementedError("LineTemplates do not have local matching logic.") + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + return "\n".join(member.to_indented_source(source_lines) for member in self.members) + + def get_instructions(self) -> list[Inst]: + insts: list[Inst] = [] + for member in self.members: + insts.extend(member.get_instructions()) + return insts + return sorted(insts, key=lambda i: i.offset) + + def __repr__(self) -> str: + name = f"{type(self).__name__}" + components = ControlFlowTemplate._indent_multiline_string("\n".join(repr(member) for member in self.members)) + return f"{name}[\n{components}]" diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/natural/LinearSequenceTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/natural/LinearSequenceTemplate.py new file mode 100644 index 0000000..7075cb9 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/natural/LinearSequenceTemplate.py @@ -0,0 +1,127 @@ +import networkx as nx + +import itertools + +from pylingual.editable_bytecode import Inst + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +# imports for our exception whitelist so we do not have to absorb any tails and affect +# control flow in the future (hopefully fingers crossed) +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ..loop.PreRefinedLoopTemplate import PreRefinedLoopTemplate + +from ...cfg_utils import get_out_edge_dict, ControlFlowEdgeType, get_dominator_function + + +class LinearSequenceTemplate(ControlFlowTemplate): + """ + A natural progression of control flow templates with the same exception handler. + No conditional jumps are allowed. + """ + + def __init__(self, *members: ControlFlowTemplate): + self.members = members + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes or not isinstance(node, ControlFlowTemplate): + return None + + dominates = get_dominator_function(cfg) + + def is_back_edge(src, dst): + return dominates(dst, src) + + base_edge_dict = get_out_edge_dict(cfg, node) + base_exception_handler = base_edge_dict["exception"] + + # validate that the current node is able to start a linear sequence + # jumps cannot start a linear sequence + if base_edge_dict["conditional"][0] or (base_edge_dict["natural"][1] and base_edge_dict["natural"][1]["type"] != ControlFlowEdgeType.NATURAL.value): + return None + # back edges cannot start a linear sequence + if any(is_back_edge(*edge) for edge in cfg.out_edges(node)): + return None + + matched_sequence = [node] + current_edge_dict = base_edge_dict + + # while there is a natural progression, try to extend the linear sequence + while (next_node_and_edge_properties := current_edge_dict["natural"])[0]: + next_node, _ = next_node_and_edge_properties + next_edge_dict = get_out_edge_dict(cfg, next_node) + + # all elements of a linear sequence must have the same exception handler + if next_edge_dict["exception"] != base_exception_handler: + break + + # only the natural incoming edge from the previous node is allowed in linear sequences + if cfg.in_degree(nbunch=next_node) > 1: + break + + # do not extend after an END_FINALLY + if isinstance(matched_sequence[-1], ControlFlowTemplate) and not isinstance(matched_sequence[-1], AbstractNonSequentiable): + insts = matched_sequence[-1].get_instructions() + if insts and insts[-1].opname == "END_FINALLY": + break + + # do not merge in prerefined loop templates; they still need to be refined + if isinstance(next_node, PreRefinedLoopTemplate): + break + + # conditional jumps are only allowed in the last element of a linear sequence + if current_edge_dict["conditional"][0] and current_edge_dict["natural"][0]: + break + + # absolute jumps are only allowed in the last element of a linear sequence + if current_edge_dict["natural"][1] and current_edge_dict["natural"][1]["type"] != ControlFlowEdgeType.NATURAL.value: + break + + matched_sequence.append(next_node) + current_edge_dict = next_edge_dict + + # if we didn't reduce the graph size, match failed + if len(matched_sequence) < 2: + return None + + # unpack nested LinearSequenceTemplates for improved readability of the parse tree + unpacked_matched_sequence = [] + for match_item in matched_sequence: + if isinstance(match_item, LinearSequenceTemplate): + unpacked_matched_sequence.extend(match_item.members) + else: + unpacked_matched_sequence.append(match_item) + # preserve the incoming edges from the first node and the outgoing edges from the last node + linear_sequence_template = LinearSequenceTemplate(*unpacked_matched_sequence) + in_edges = ((linear_sequence_template if src in matched_sequence else src, linear_sequence_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = ((linear_sequence_template, linear_sequence_template if dst in matched_sequence else dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(nbunch=matched_sequence[-1], data=True)) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from(matched_sequence) + reduced_cfg.add_node(linear_sequence_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + return "\n".join(member.to_indented_source(source_lines) for member in self.members) + + def get_instructions(self) -> list[Inst]: + insts: list[Inst] = [] + for member in self.members: + insts.extend(member.get_instructions()) + return insts + return sorted(insts, key=lambda i: i.offset) + + def __repr__(self) -> str: + name = f"{type(self).__name__}" + components = ControlFlowTemplate._indent_multiline_string("\n".join(repr(member) for member in self.members)) + return f"{name}[\n{components}]" diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/placeholders/ExceptPlaceholderTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/placeholders/ExceptPlaceholderTemplate.py new file mode 100644 index 0000000..e4f3b47 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/placeholders/ExceptPlaceholderTemplate.py @@ -0,0 +1,30 @@ +import networkx as nx + + +from ..abstract.AbstractTemplate import ControlFlowTemplate + + + + + +class ExceptPlaceholderTemplate(ControlFlowTemplate): + """ + Placeholder for except; used in ExceptAs.py + """ + + def __init__(self, body: ControlFlowTemplate): + self.body = body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + raise NotImplementedError("ExceptPlaceholderTemplate does not have local matching logic. These are created in ExceptAs") + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + body = ControlFlowTemplate._indent_multiline_string(self.body.to_indented_source(source_lines)) + return f"except:\n{body}" + + def __repr__(self) -> str: + return super().__repr__() if self.body else type(self).__name__ diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/placeholders/IrreduciblePlaceholderTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/placeholders/IrreduciblePlaceholderTemplate.py new file mode 100644 index 0000000..0edc9c2 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/placeholders/IrreduciblePlaceholderTemplate.py @@ -0,0 +1,9 @@ +from ..abstract.AbstractTemplate import ControlFlowTemplate + + +class IrreduciblePlaceholderTemplate(ControlFlowTemplate): + def __init__(self, msg): + self.msg = msg + + def to_indented_source(self, source_lines: list[str]) -> str: + return f"pass # cflow: {self.msg}" diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/placeholders/WhileTruePlaceholderTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/placeholders/WhileTruePlaceholderTemplate.py new file mode 100644 index 0000000..0e85f0c --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/placeholders/WhileTruePlaceholderTemplate.py @@ -0,0 +1,42 @@ +import networkx as nx + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import ControlFlowEdgeType + + +class WhileTruePlaceholderTemplate(ControlFlowTemplate): + """ + Placeholder for While True; used in PreRefinedLoopTemplate + """ + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + raise NotImplementedError("WhileTruePlaceholderTemplate does not have local matching logic. These are created in PreRefinedLoopTemplate") + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + return "while True: # inserted" + + @staticmethod + def structure_node_inplace(cfg: nx.DiGraph, loop_header, loop_successor): + # insert a WhileTruePlaceholderTemplate before the loop_header, and add a conditional edge to the loop successor + # this "looks like" a normal while loop, which allows structuring to continue + placeholder = WhileTruePlaceholderTemplate() + + # replace the incoming edges + in_edges = [(src, placeholder, data) for src, _, data in cfg.in_edges(loop_header, data=True)] + cfg.add_edges_from(in_edges) + cfg.remove_edges_from(list(cfg.in_edges(loop_header))) + + # add outgoing edges to the placeholder + cfg.add_edge(placeholder, loop_header, type=ControlFlowEdgeType.NATURAL.value) + if loop_successor: + cfg.add_edge(placeholder, loop_successor, type=ControlFlowEdgeType.FALSE_JUMP.value) + + return placeholder + + def __repr__(self) -> str: + return type(self).__name__ diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/subtemplates/OptionalExitSubtemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/subtemplates/OptionalExitSubtemplate.py new file mode 100644 index 0000000..22c3fae --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/subtemplates/OptionalExitSubtemplate.py @@ -0,0 +1,130 @@ +import networkx as nx + +import itertools + + +from ..abstract.AbstractTemplate import ControlFlowTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import assert_edge_type, optional_node, optional_edge, assert_in_degree, node_is_none_or_matches, edge_is_none_or_matches + + +class ExitSubTemplate(ControlFlowTemplate): + """ + + A basic with template as a catch all for exits + first case + + (1) or simply (1) + e/ \ + (2) + + + """ + + _subgraph = { + "exit_header": TemplateNode( + natural_edge=TemplateEdge(source="exit_header", dest="exit_flow", edge_verification_func=edge_is_none_or_matches(assert_edge_type(ControlFlowEdgeType.NATURAL))), + exception_edge=TemplateEdge( + source="exit_header", + dest="exception_handler", + edge_verification_func=optional_edge, + ), + ), + "exit_flow": TemplateNode(node_verification_func=node_is_none_or_matches(assert_in_degree(1)), exception_edge=TemplateEdge(source="exit_flow", dest="outer_exception_handler", edge_verification_func=optional_edge)), + "exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, exit_header: ControlFlowTemplate, exit_flow: ControlFlowTemplate): + self.exit_header = exit_header + self.exit_flow = exit_flow + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template or we are happy and return the base cfg. + Otherwise, returns None. + """ + + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher( + template_node_dict=ExitSubTemplate._subgraph, + root_key="exit_header", + mapping_verification_func=None, + ) + + mapping = matcher.match_at_graph_node(cfg, node) + + # not standard; if we didn't match the exit, then continue matching the rest of the parent template + if not mapping: + return cfg + + # this is an appropriate match, but there is nothing to do + if not mapping["exit_flow"]: + return cfg + + exit_template = ExitSubTemplate( + exit_header=mapping["exit_header"], + exit_flow=mapping["exit_flow"], + ) + + in_edges = ((src, exit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = [] + + if mapping["exception_handler"]: + out_edges.append((exit_template, mapping["exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([exit_template.exit_flow, exit_template.exit_header]) + reduced_cfg.add_node(exit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + header = self.exit_header.to_indented_source(source_lines) + exit_flow = self.exit_flow.to_indented_source(source_lines) + return "\n".join([header, exit_flow]) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsCleanup.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsCleanup.py new file mode 100644 index 0000000..a39affc --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsCleanup.py @@ -0,0 +1,139 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ..abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import assert_edge_type, optional_node, optional_edge, assert_in_degree, node_match_all, node_match_any, contains_opname_sequence, edge_is_none_or_matches + + +class ExceptAsCleanupTemplate(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + """ + The boilerplate cleanup at the end of an `except as` block. + The "happy cleanup" (1) is when there is no exception, and it jumps out to the next code segment. + The "angry cleanup" (2) is when there is an exception, and it reraises. + (0) + / \\e --> (012) + (1) (2) |j + |j (3) + (3) + """ + + _subgraph = { + "except_body": TemplateNode( + natural_edge=TemplateEdge( + source="except_body", + dest="happy_cleanup", + ), + exception_edge=TemplateEdge( + source="except_body", + dest="angry_cleanup", + ), + ), + "happy_cleanup": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + node_match_any( + contains_opname_sequence( + "LOAD_CONST", + "STORE_NAME", + "DELETE_NAME", + ), + contains_opname_sequence( + "LOAD_CONST", + "STORE_FAST", + "DELETE_FAST", + ), + ), + ), + natural_edge=TemplateEdge(source="happy_cleanup", dest=None, edge_verification_func=edge_is_none_or_matches(assert_edge_type(ControlFlowEdgeType.JUMP))), + exception_edge=TemplateEdge( + source="happy_cleanup", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "angry_cleanup": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + node_match_any( + contains_opname_sequence("LOAD_CONST", "STORE_NAME", "DELETE_NAME", "RERAISE"), + contains_opname_sequence("LOAD_CONST", "STORE_FAST", "DELETE_FAST", "RERAISE"), + ), + ), + exception_edge=TemplateEdge( + source="angry_cleanup", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_body: ControlFlowTemplate, happy_cleanup: ControlFlowTemplate, angry_cleanup: ControlFlowTemplate): + self.except_body = except_body + self.happy_cleanup = happy_cleanup + self.angry_cleanup = angry_cleanup + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptAsCleanupTemplate._subgraph, root_key="except_body", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + except_as_cleanup_template = ExceptAsCleanupTemplate(except_body=mapping["except_body"], happy_cleanup=mapping.get("happy_cleanup", None), angry_cleanup=mapping.get("angry_cleanup", None)) + + in_edges = ((src, except_as_cleanup_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + out_edges = [(except_as_cleanup_template, dst, data) for _, dst, data in cfg.out_edges([except_as_cleanup_template.happy_cleanup, except_as_cleanup_template.angry_cleanup], data=True)] + if mapping["outer_exception_handler"]: + out_edges.append((except_as_cleanup_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([except_as_cleanup_template.except_body, except_as_cleanup_template.happy_cleanup, except_as_cleanup_template.angry_cleanup]) + reduced_cfg.add_node(except_as_cleanup_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + # cleanup code is implicit! only report the body code + return self.except_body.to_indented_source(source_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsExceptTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsExceptTemplate.py new file mode 100644 index 0000000..1f8e0af --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsExceptTemplate.py @@ -0,0 +1,154 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ..abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_except_as + +from ..placeholders.ExceptPlaceholderTemplate import ExceptPlaceholderTemplate +from .ExceptAsTemplate import ExceptAsTemplate + + +class ExceptAsExceptTemplate(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + """ + An `except as` block, after its cleanup has been structured. + If there are multiple, this will match the last block in the series and set up the next one to be matched + (0) + / \\j --> (012) + (1) (2) |j + \\j //j (3) + (3) + """ + + _subgraph = { + "except_as_header": TemplateNode( + node_verification_func=assert_except_as, + natural_edge=TemplateEdge( + source="except_as_header", + dest="except_body", + ), + conditional_edge=TemplateEdge(source="except_as_header", dest="non_match_path"), + exception_edge=TemplateEdge(source="except_as_header", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="except_body", + dest="after_except", + ), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "non_match_path": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="non_match_path", + dest="after_except", + ), + exception_edge=TemplateEdge( + source="non_match_path", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "after_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_as_header: ControlFlowTemplate, except_body: ControlFlowTemplate, non_match_path: ControlFlowTemplate): + self.except_as_header = except_as_header + self.except_body = except_body + self.non_match_path = non_match_path + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptAsExceptTemplate._subgraph, root_key="except_as_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + non_match_path = mapping["non_match_path"] + if not isinstance(non_match_path, ExceptAsExceptTemplate) and not isinstance(non_match_path, ExceptAsTemplate): + non_match_path = ExceptPlaceholderTemplate(body=non_match_path) + + except_as_cleanup_template = ExceptAsExceptTemplate(except_as_header=mapping["except_as_header"], except_body=mapping["except_body"], non_match_path=non_match_path) + + in_edges = ((src, except_as_cleanup_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((except_as_cleanup_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + if mapping["after_except"]: + out_edges.append((except_as_cleanup_template, mapping["after_except"], {"type": ControlFlowEdgeType.JUMP.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([except_as_cleanup_template.except_as_header, except_as_cleanup_template.except_body, mapping["non_match_path"]]) + reduced_cfg.add_node(except_as_cleanup_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.except_as_header.to_indented_source(source_lines).rstrip() + body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)).rstrip() + non_match = self.non_match_path.to_indented_source(source_lines).rstrip() + return f"{header}\n{body}\n{non_match}" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsExitTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsExitTemplate.py new file mode 100644 index 0000000..449a9e5 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsExitTemplate.py @@ -0,0 +1,159 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ..abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate + + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_except_as, node_match_all, node_match_any, contains_opname_sequence + + + +class ExceptAsExitTemplate(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + """ + An `except as` block, but with an exit statement. + If there are multiple, this will match the last block in the series and set up the next one to be matched + (0) + / \\j --> (01234) + (1) (2) + | + (3) + |e + (4) + """ + + _subgraph = { + "except_as_header": TemplateNode( + node_verification_func=assert_except_as, + natural_edge=TemplateEdge( + source="except_as_header", + dest="except_body_setup", + ), + conditional_edge=TemplateEdge(source="except_as_header", dest="non_match_path"), + exception_edge=TemplateEdge(source="except_as_header", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "except_body_setup": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="except_body_setup", dest="except_body", edge_verification_func=optional_edge), + exception_edge=TemplateEdge( + source="except_body_setup", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="except_body", + dest="except_as_cleanup", + edge_verification_func=optional_edge, + ), + ), + "except_as_cleanup": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + node_match_any( + contains_opname_sequence("LOAD_CONST", "STORE_NAME", "DELETE_NAME", "RERAISE"), + contains_opname_sequence("LOAD_CONST", "STORE_FAST", "DELETE_FAST", "RERAISE"), + contains_opname_sequence("LOAD_CONST", "STORE_NAME", "DELETE_NAME", "END_FINALLY"), + contains_opname_sequence("LOAD_CONST", "STORE_FAST", "DELETE_FAST", "END_FINALLY"), + ), + ), + exception_edge=TemplateEdge( + source="except_as_cleanup", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "non_match_path": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="non_match_path", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="non_match_path", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="non_match_path", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_as_header: ControlFlowTemplate, except_body_setup: ControlFlowTemplate, except_body: ControlFlowTemplate, except_as_cleanup: ControlFlowTemplate, non_match_path: ControlFlowTemplate): + self.except_as_header = except_as_header + self.except_body_setup = except_body_setup + self.except_body = except_body + self.except_as_cleanup = except_as_cleanup + self.non_match_path = non_match_path + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptAsExitTemplate._subgraph, root_key="except_as_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + except_as_exit_template = ExceptAsExitTemplate( + except_as_header=mapping["except_as_header"], except_body_setup=mapping["except_body_setup"], except_body=mapping["except_body"], except_as_cleanup=mapping["except_as_cleanup"], non_match_path=mapping["non_match_path"] + ) + + in_edges = ((src, except_as_exit_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + out_edges = ((except_as_exit_template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(except_as_exit_template.non_match_path, data=True)) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from( + [except_as_exit_template.except_as_header, except_as_exit_template.except_body_setup, except_as_exit_template.except_body, except_as_exit_template.except_as_cleanup, except_as_exit_template.non_match_path] + ) + reduced_cfg.add_node(except_as_exit_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.except_as_header.to_indented_source(source_lines) + self.except_body_setup.to_indented_source(source_lines) + body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)) + non_match = self.non_match_path.to_indented_source(source_lines) + return f"{header}\n{body}\n{non_match}" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsTemplate.py new file mode 100644 index 0000000..87d8442 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptAsTemplate.py @@ -0,0 +1,141 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ..abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_except_as + + + +class ExceptAsTemplate(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + """ + An `except as` block, after its cleanup has been structured. + If there are multiple, this will match the last block in the series and set up the next one to be matched + (0) + / \\j --> (012) + (1) (2) |j + |j (3) + (3) + """ + + _subgraph = { + "except_as_header": TemplateNode( + node_verification_func=assert_except_as, + natural_edge=TemplateEdge( + source="except_as_header", + dest="except_body", + ), + conditional_edge=TemplateEdge(source="except_as_header", dest="non_match_path"), + exception_edge=TemplateEdge(source="except_as_header", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="except_body", dest="after_except", edge_verification_func=optional_edge), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "non_match_path": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="non_match_path", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "after_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_as_header: ControlFlowTemplate, except_body: ControlFlowTemplate, non_match_path: ControlFlowTemplate): + self.except_as_header = except_as_header + self.except_body = except_body + self.non_match_path = non_match_path + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptAsTemplate._subgraph, root_key="except_as_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + except_as_cleanup_template = ExceptAsTemplate(except_as_header=mapping["except_as_header"], except_body=mapping["except_body"], non_match_path=mapping["non_match_path"]) + + in_edges = ((src, except_as_cleanup_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((except_as_cleanup_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + if mapping["after_except"]: + out_edges.append((except_as_cleanup_template, mapping["after_except"], {"type": ControlFlowEdgeType.JUMP.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([except_as_cleanup_template.except_as_header, except_as_cleanup_template.except_body, except_as_cleanup_template.non_match_path]) + reduced_cfg.add_node(except_as_cleanup_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.except_as_header.to_indented_source(source_lines) + body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)) + non_match = self.non_match_path.to_indented_source(source_lines) + return f"{header}\n{body}\n{non_match}" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptException.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptException.py new file mode 100644 index 0000000..036da5a --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/ExceptException.py @@ -0,0 +1,120 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ..abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_except_as + + + +class ExceptException(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + """ + An `except exception block' currently confirmed to work from 3.6 to 3.8 + If there are multiple, this will match the last block in the series and set up the next one to be matched + (0) + / \\j --> (012) + (1) (2) |j + + """ + + _subgraph = { + "except_as_header": TemplateNode( + node_verification_func=assert_except_as, + natural_edge=TemplateEdge( + source="except_as_header", + dest="except_body", + ), + conditional_edge=TemplateEdge(source="except_as_header", dest="non_match_path"), + exception_edge=TemplateEdge(source="except_as_header", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + # natural_edge=TemplateEdge( + # source='except_body', + # dest=None, #might need a tail I am not too sure about any potential natural edges as of now + # edge_verification_func=optional_edge + # ), + exception_edge=TemplateEdge(source="except_body", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "non_match_path": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="non_match_path", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_as_header: ControlFlowTemplate, except_body: ControlFlowTemplate, non_match_path: ControlFlowTemplate): + self.except_as_header = except_as_header + self.except_body = except_body + self.non_match_path = non_match_path + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptException._subgraph, root_key="except_as_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + except_exception_template = ExceptException(except_as_header=mapping["except_as_header"], except_body=mapping["except_body"], non_match_path=mapping["non_match_path"]) + + in_edges = ((src, except_exception_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((except_exception_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([except_exception_template.except_as_header, except_exception_template.except_body, except_exception_template.non_match_path]) + reduced_cfg.add_node(except_exception_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.except_as_header.to_indented_source(source_lines) + body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)) + non_match = self.non_match_path.to_indented_source(source_lines) + return f"{header}\n{body}\n{non_match}" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/FinallyTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/FinallyTemplate.py new file mode 100644 index 0000000..57f773a --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/FinallyTemplate.py @@ -0,0 +1,167 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, assert_instruction_opname + + + +class FinallyTemplate(ControlFlowTemplate, AbstractNonSequentiable): + """ + A basic `finally` block after the related try-excepts have been structured. + (0) + / \\ --> (01234) + |e (2) + | /e| + (3) (4) + does not cover additional finally blocks that will be inserted in the bytecode as a result of returns / breaking out of loops + """ + + _subgraph = { + "setup_finally": TemplateNode( + node_verification_func=assert_instruction_opname("SETUP_FINALLY"), + natural_edge=TemplateEdge( + source="setup_finally", + dest="try_except", + ), + exception_edge=TemplateEdge( + source="setup_finally", + dest="angry_finally", + ), + ), + "try_except": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="try_except", + dest="happy_finally", + ), + exception_edge=TemplateEdge( + source="try_except", + dest="angry_finally", + edge_verification_func=optional_edge, + ), + ), + "happy_finally": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="happy_finally", dest="tail", edge_verification_func=optional_edge), + exception_edge=TemplateEdge( + source="happy_finally", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "angry_finally": TemplateNode( + node_verification_func=assert_in_degree(2), + exception_edge=TemplateEdge( + source="angry_finally", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, setup_finally: ControlFlowTemplate, try_except: ControlFlowTemplate, happy_finally: ControlFlowTemplate, angry_finally: ControlFlowTemplate): + self.setup_finally = setup_finally + self.try_except = try_except + self.happy_finally = happy_finally + self.angry_finally = angry_finally + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + def verify_finally_match(cfg: nx.DiGraph, mapping: dict[str, ControlFlowTemplate]) -> bool: + # check to make sure that all non-stack/control instructions match between the two finally blocks + # this list was made for 3.9, so it may need to be expanded for other versions + stack_and_control_insts = {"POP_TOP", "POP_EXCEPT", "ROT_TWO", "ROT_THREE", "ROT_FOUR", "JUMP_FORWARD", "JUMP_BACKWARD", "JUMP_ABSOLUTE", "RERAISE"} + happy_insts = [(inst.opname, inst.arg) for inst in mapping["happy_finally"].get_instructions() if inst.opname not in stack_and_control_insts] + angry_insts = [(inst.opname, inst.arg) for inst in mapping["angry_finally"].get_instructions() if inst.opname not in stack_and_control_insts] + return happy_insts == angry_insts + + matcher = GraphTemplateMatcher(template_node_dict=FinallyTemplate._subgraph, root_key="setup_finally", mapping_verification_func=verify_finally_match) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + finally_template = FinallyTemplate(setup_finally=mapping["setup_finally"], try_except=mapping["try_except"], happy_finally=mapping["happy_finally"], angry_finally=mapping["angry_finally"]) + + in_edges = [(src, finally_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)] + # only preserve exception handling edges + # insert a continuation edge to after the finally + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((finally_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + if mapping["tail"]: + out_edges.append((finally_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([finally_template.setup_finally, finally_template.try_except, finally_template.happy_finally, finally_template.angry_finally]) + reduced_cfg.add_node(finally_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + try_block = self.try_except.to_indented_source(source_lines) + # pick one of the finally bodies to get the source code from + finally_body = ControlFlowTemplate._indent_multiline_string(self.happy_finally.to_indented_source(source_lines)) + if not finally_body: + finally_body = ControlFlowTemplate._indent_multiline_string(self.angry_finally.to_indented_source(source_lines)) + finally_lines = [try_block, "finally: # inserted", finally_body] + return "\n".join(finally_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/GeneratorCleanupTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/GeneratorCleanupTemplate.py new file mode 100644 index 0000000..2ea4505 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/GeneratorCleanupTemplate.py @@ -0,0 +1,70 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate + + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import assert_in_degree, node_match_all + + +def is_cleanup(cfg: nx.DiGraph, node) -> bool: + i = node.get_instructions() + return len(i) == 2 and i[0].opname == "CALL_INTRINSIC_1" and i[1].opname == "RERAISE" + + +class GeneratorCleanupTemplate(ControlFlowTemplate): + _subgraph = { + "generator": TemplateNode( + exception_edge=TemplateEdge( + source="generator", + dest="cleanup", + ) + ), + "cleanup": TemplateNode( + node_verification_func=node_match_all(assert_in_degree(1), is_cleanup), + ), + } + + def __init__(self, generator: ControlFlowTemplate, cleanup: ControlFlowTemplate): + self.generator = generator + self.cleanup = cleanup + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=GeneratorCleanupTemplate._subgraph, root_key="generator", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + template = GeneratorCleanupTemplate(generator=mapping["generator"], cleanup=mapping["cleanup"]) + + in_edges = ((src, template, edge_properties) for src, dst, edge_properties in cfg.in_edges(nbunch=node, data=True)) + out_edges = ((template, dst, edge_properties) for src, dst, edge_properties in cfg.out_edges(nbunch=node, data=True) if dst != mapping["cleanup"]) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([template.cleanup, template.generator]) + reduced_cfg.add_node(template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + return self.generator.to_indented_source(source_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryExceptElseTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryExceptElseTemplate.py new file mode 100644 index 0000000..410b29a --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryExceptElseTemplate.py @@ -0,0 +1,167 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from .TryExceptTemplate import TryExceptTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import assert_edge_type, optional_node, optional_edge, assert_in_degree, edge_is_none_or_matches + + + +class TryExceptElseTemplate(ControlFlowTemplate, AbstractNonSequentiable): + """ + A `try-except` with an else and a structured except. + (0) + / \\e --> (0123) + (1) (2) | + |j |j (4) + (3) | + \\ / + (4) + """ + + _subgraph = { + "try_body": TemplateNode( + natural_edge=TemplateEdge( + source="try_body", + dest="try_footer", + ), + exception_edge=TemplateEdge( + source="try_body", + dest="except_body", + ), + ), + "try_footer": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="try_footer", dest="else_body", edge_verification_func=assert_edge_type(ControlFlowEdgeType.JUMP)), + exception_edge=TemplateEdge( + source="try_footer", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "else_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="else_body", + dest="after_try_except", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="else_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="except_body", + dest="after_try_except", + edge_verification_func=edge_is_none_or_matches(assert_edge_type(ControlFlowEdgeType.JUMP)), + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "after_try_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, try_body: ControlFlowTemplate, try_footer: ControlFlowTemplate, else_body: ControlFlowTemplate, except_body: ControlFlowTemplate): + self.try_body = try_body + self.try_footer = try_footer + self.else_body = else_body + self.except_body = except_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=TryExceptElseTemplate._subgraph, root_key="try_body", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + try_except_template = TryExceptElseTemplate(try_body=mapping["try_body"], try_footer=mapping["try_footer"], else_body=mapping["else_body"], except_body=mapping["except_body"]) + + in_edges = ((src, try_except_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + # insert a continuation edge to after the try except + out_edges = [] + if mapping.get("after_try_except", None): + out_edges.append((try_except_template, mapping["after_try_except"], {"type": ControlFlowEdgeType.NATURAL.value})) + if mapping["outer_exception_handler"]: + out_edges.append((try_except_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([try_except_template.try_body, try_except_template.try_footer, try_except_template.else_body, try_except_template.except_body]) + reduced_cfg.add_node(try_except_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + try_except_template = TryExceptTemplate(try_body=self.try_body, try_footer=self.try_footer, except_body=self.except_body) + try_except_lines = [try_except_template.to_indented_source(source_lines)] + else_body = ControlFlowTemplate._indent_multiline_string(self.else_body.to_indented_source(source_lines)) + try_except_lines.extend(["else: # inserted", else_body]) + + return "\n".join(try_except_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryExceptTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryExceptTemplate.py new file mode 100644 index 0000000..26f72af --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryExceptTemplate.py @@ -0,0 +1,188 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ..abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate +from ..loop.LoopExitTemplate import LoopExitTemplate + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import optional_node, optional_edge, assert_in_degree, node_match_all, assert_node_has_no_backwards_edges, node_is_none_or_matches + +from .ExceptAsTemplate import ExceptAsTemplate +from .ExceptAsExceptTemplate import ExceptAsExceptTemplate +from ..subtemplates.OptionalExitSubtemplate import ExitSubTemplate + + +class TryExceptTemplate(ControlFlowTemplate, AbstractNonSequentiable): + """ + A `try-except` block with just a naked except. + (0) + / \\e --> (012) + (1) (2) | + \\j /j (3) + (3) + One or more of the try/except may have no further control flow. + However, if both have successors, they must go to the same place. + """ + + _subgraph = { + "try_body": TemplateNode( + natural_edge=TemplateEdge( + source="try_body", + dest="try_footer", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="try_body", + dest="except_body", + ), + ), + "try_footer": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=node_is_none_or_matches( + node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + ) + ), + natural_edge=TemplateEdge(source="try_footer", dest="after_try_except", edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="try_footer", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "except_body": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + ), + natural_edge=TemplateEdge(source="except_body", dest="after_try_except", edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "after_try_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_try_except", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, try_body: ControlFlowTemplate, try_footer: ControlFlowTemplate, except_body: ControlFlowTemplate): + self.try_body = try_body + self.try_footer = try_footer + self.except_body = except_body + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=TryExceptTemplate._subgraph, root_key="try_body", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + # have to make sure there is a try_footer before trying to map it as it is an optional node (this is mostly here for 3.7 + # since there are cases where there is not a try footer at all) + + try_except_template = TryExceptTemplate(try_body=mapping["try_body"], try_footer=mapping.get("try_footer", None), except_body=mapping["except_body"]) + + in_edges = ((src, try_except_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + # insert a continuation edge to after the try except + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((try_except_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + if "after_try_except" in mapping.keys(): + after_try_except = mapping["after_try_except"] + out_edges.append((try_except_template, after_try_except, {"type": ControlFlowEdgeType.NATURAL.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([try_except_template.try_body, try_except_template.try_footer, try_except_template.except_body]) + reduced_cfg.add_node(try_except_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + try_body = ControlFlowTemplate._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + + # check if there is a try footer as in 3.7 there may not be a try footer at all + if self.try_footer: + try_footer = ControlFlowTemplate._indent_multiline_string(self.try_footer.to_indented_source(source_lines)) + else: + try_footer = "" + + try_except_lines = ["try:", try_body, try_footer] + + # if we matched against an "Except ... as" chain, then omit the inserted except: block + omit_except = False + if isinstance(self.except_body, AbstractExceptionBlockTemplate): + omit_except = True + elif isinstance(self.except_body, LoopExitTemplate): + if isinstance(self.except_body.tail, ExceptAsTemplate) or isinstance(self.except_body.tail, ExceptAsExceptTemplate): + omit_except = True + + except_body = self.except_body.to_indented_source(source_lines) + if not omit_except: + try_except_lines.append("except:") + except_body = ControlFlowTemplate._indent_multiline_string(except_body) + + try_except_lines.append(except_body) + + return "\n".join(try_except_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryFinallyTemplate.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryFinallyTemplate.py new file mode 100644 index 0000000..057848a --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/TryFinallyTemplate.py @@ -0,0 +1,173 @@ +import networkx as nx + +import itertools + +from ..abstract.AbstractTemplate import ControlFlowTemplate +from ..abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable + +from ...cfg_utils import ControlFlowEdgeType + +from ..Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ..match_utils import assert_edge_type, optional_node, optional_edge, assert_in_degree, assert_instruction_opname, edge_is_none_or_matches + + + +class TryFinallyTemplate(ControlFlowTemplate, AbstractNonSequentiable): + """ + A `try` block with only a `finally` following it. + (0) + | + (1) + /e\\ --> (0123) + (3) (2) | + |j (4) + (4) + does not cover additional finally blocks that will be inserted in the bytecode as a result of returns / breaking out of loops + """ + + _subgraph = { + "setup_finally": TemplateNode( + node_verification_func=assert_instruction_opname("SETUP_FINALLY"), + natural_edge=TemplateEdge( + source="setup_finally", + dest="try_body", + ), + exception_edge=TemplateEdge(source="setup_finally", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "try_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="try_body", + dest="happy_finally", + ), + exception_edge=TemplateEdge( + source="try_body", + dest="angry_finally", + ), + ), + "happy_finally": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="happy_finally", dest="tail", edge_verification_func=edge_is_none_or_matches(assert_edge_type(ControlFlowEdgeType.JUMP))), + exception_edge=TemplateEdge( + source="happy_finally", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "angry_finally": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="angry_finally", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, setup_finally: ControlFlowTemplate, try_body: ControlFlowTemplate, happy_finally: ControlFlowTemplate, angry_finally: ControlFlowTemplate): + self.setup_finally = setup_finally + self.try_body = try_body + self.happy_finally = happy_finally + self.angry_finally = angry_finally + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + if cfg.in_degree(node) != 1: + return None + + # to avoid being treated as a try-except, we actually need to greedily search up one layer + pred = next(cfg.predecessors(node)) + + def verify_finally_match(cfg: nx.DiGraph, mapping: dict[str, ControlFlowTemplate]) -> bool: + # check to make sure that all non-stack/control instructions match between the two finally blocks + # this list was made for 3.9, so it may need to be expanded for other versions + stack_and_control_insts = {"POP_TOP", "POP_EXCEPT", "ROT_TWO", "ROT_THREE", "ROT_FOUR", "JUMP_FORWARD", "JUMP_BACKWARD", "JUMP_ABSOLUTE", "RERAISE"} + happy_insts = [(inst.opname, inst.arg) for inst in mapping["happy_finally"].get_instructions() if inst.opname not in stack_and_control_insts] + angry_insts = [(inst.opname, inst.arg) for inst in mapping["angry_finally"].get_instructions() if inst.opname not in stack_and_control_insts] + return happy_insts == angry_insts + + matcher = GraphTemplateMatcher(template_node_dict=TryFinallyTemplate._subgraph, root_key="setup_finally", mapping_verification_func=verify_finally_match) + + mapping = matcher.match_at_graph_node(cfg, pred) + + if not mapping: + return None + + finally_template = TryFinallyTemplate(setup_finally=mapping["setup_finally"], try_body=mapping["try_body"], happy_finally=mapping["happy_finally"], angry_finally=mapping["angry_finally"]) + + in_edges = [(src, finally_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(finally_template.setup_finally, data=True)] + # only preserve exception handling edges + # insert a continuation edge to after the finally + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((finally_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + if mapping["tail"]: + out_edges.append((finally_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([finally_template.setup_finally, finally_template.try_body, finally_template.happy_finally, finally_template.angry_finally]) + reduced_cfg.add_node(finally_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + # sometimes the setup finally is included in a linear sequence, so we need to include that source + setup_finally = self.setup_finally.to_indented_source(source_lines) + try_block = ControlFlowTemplate._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + # pick one of the finally bodies to get the source code from + finally_body = ControlFlowTemplate._indent_multiline_string(self.happy_finally.to_indented_source(source_lines)) + if not finally_body: + finally_body = ControlFlowTemplate._indent_multiline_string(self.angry_finally.to_indented_source(source_lines)) + finally_lines = [setup_finally, "try:", try_block, "finally: # inserted", finally_body] + return "\n".join(finally_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsCleanupSubTemplate311.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsCleanupSubTemplate311.py new file mode 100644 index 0000000..902efe7 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsCleanupSubTemplate311.py @@ -0,0 +1,159 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ...abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import optional_node, optional_edge, assert_in_degree, node_match_all, node_match_any, contains_opname_sequence + + +class ExceptAsCleanupSubTemplate311(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + """ + The boilerplate cleanup at the end of an `except as` block after 3.11. + The "happy cleanup" (3) is when there is no exception, and it jumps out to the next code segment (except footer in 3.11). + The "angry cleanup" (2) is when there is an exception, and it reraises. + (0) + | \\e + (1) | + / |e | --> (012) + (3)(2) | | \\e + |e / (3) (4) + (4) + """ + + _subgraph = { + "except_header": TemplateNode( + natural_edge=TemplateEdge( + source="except_header", + dest="except_body", + ), + exception_edge=TemplateEdge( + source="except_body", + dest="panic_except", + ), + ), + "except_body": TemplateNode( + natural_edge=TemplateEdge( + source="except_body", + dest="happy_cleanup", + ), + exception_edge=TemplateEdge( + source="except_body", + dest="angry_cleanup", + ), + ), + "happy_cleanup": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + node_match_any( + contains_opname_sequence( + "LOAD_CONST", + "STORE_NAME", + "DELETE_NAME", + ), + contains_opname_sequence( + "LOAD_CONST", + "STORE_FAST", + "DELETE_FAST", + ), + ), + ), + natural_edge=TemplateEdge(source="happy_cleanup", dest=None, edge_verification_func=optional_edge), + exception_edge=TemplateEdge( + source="happy_cleanup", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "angry_cleanup": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + node_match_any( + contains_opname_sequence("LOAD_CONST", "STORE_NAME", "DELETE_NAME", "RERAISE"), + contains_opname_sequence("LOAD_CONST", "STORE_FAST", "DELETE_FAST", "RERAISE"), + ), + ), + exception_edge=TemplateEdge( + source="angry_cleanup", + dest="panic_except", + ), + ), + "panic_except": TemplateNode( + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ) + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_header: ControlFlowTemplate, except_body: ControlFlowTemplate, angry_cleanup: ControlFlowTemplate): + self.except_header = except_header + self.except_body = except_body + self.angry_cleanup = angry_cleanup + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptAsCleanupSubTemplate311._subgraph, root_key="except_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return cfg # if we didn't match the subtemplate, keep trying with the main template + + except_as_cleanup_template = ExceptAsCleanupSubTemplate311(except_header=mapping["except_header"], except_body=mapping["except_body"], angry_cleanup=mapping["angry_cleanup"]) + + in_edges = ((src, except_as_cleanup_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + out_edges = [] + if mapping["happy_cleanup"]: + out_edges.append((except_as_cleanup_template, mapping["happy_cleanup"], {"type": ControlFlowEdgeType.NATURAL.value})) + if mapping["panic_except"]: + out_edges.append((except_as_cleanup_template, mapping["panic_except"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([except_as_cleanup_template.except_header, except_as_cleanup_template.except_body, except_as_cleanup_template.angry_cleanup]) + reduced_cfg.add_node(except_as_cleanup_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + # cleanup code is implicit! only report the body code + return self.except_body.to_indented_source(source_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsNonMatchSubtemplate311.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsNonMatchSubtemplate311.py new file mode 100644 index 0000000..0847860 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsNonMatchSubtemplate311.py @@ -0,0 +1,228 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ...abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate + +from ...subtemplates.OptionalExitSubtemplate import ExitSubTemplate + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import optional_node, optional_edge, assert_in_degree, node_is_none_or_matches, assert_instruction_opname, assert_node_type + + + +class ExceptAsNonMatchSubTemplate311(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + """ + The non-match path of an except-as, which can be: + 1. a standalone reraise (end of an except as chain) + 2. an except block, which may exit + 3. a structured except-as + """ + + _reraise_subgraph = { + "reraise": TemplateNode(node_verification_func=assert_instruction_opname("RERAISE"), exception_edge=TemplateEdge(source="reraise", dest="panic_except")), + "panic_except": TemplateNode( + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ) + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + _except_subgraph = { + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="except_body", + dest="except_footer", + ), + exception_edge=TemplateEdge( + source="except_body", + dest="panic_except", + ), + ), + "except_footer": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=node_is_none_or_matches(assert_in_degree(1)), + natural_edge=TemplateEdge( + source="except_footer", + dest="after_except", + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="except_footer", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "panic_except": TemplateNode( + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ) + ), + "after_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + _structered_except_as_subgraph = { + "except_as": TemplateNode(node_verification_func=assert_node_type(AbstractNonSequentiable), natural_edge=TemplateEdge(source="except_as", dest=None), exception_edge=TemplateEdge(source="except_as", dest="panic_except")), + "panic_except": TemplateNode( + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ) + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_body: ControlFlowTemplate, except_footer: ControlFlowTemplate): + self.except_body = except_body + self.except_footer = except_footer + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + # start by trying to match reraise + matcher = GraphTemplateMatcher(template_node_dict=ExceptAsNonMatchSubTemplate311._reraise_subgraph, root_key="reraise", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if mapping: + # single-node subgraph does not need to by updated + return cfg + + # didn't match reraise; try to match structured except as + matcher = GraphTemplateMatcher(template_node_dict=ExceptAsNonMatchSubTemplate311._structered_except_as_subgraph, root_key="except_as", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if mapping: + # single-node subgraph does not need to by updated + return cfg + + # didn't match structured except as; try to match except block + matcher = GraphTemplateMatcher(template_node_dict=ExceptAsNonMatchSubTemplate311._except_subgraph, root_key="except_body", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + except_template = ExceptAsNonMatchSubTemplate311(except_body=mapping["except_body"], except_footer=mapping["except_footer"]) + + in_edges = ((src, except_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + out_edges = [] + if mapping["panic_except"]: + out_edges.append((except_template, mapping["panic_except"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + if mapping["after_except"]: + out_edges.append((except_template, mapping["after_except"], {"type": ControlFlowEdgeType.JUMP.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_node(except_template.except_body) + if except_template.except_footer: + reduced_cfg.remove_node(except_template.except_footer) + reduced_cfg.add_node(except_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + + except_lines = ["except:"] + body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)) + except_lines.append(body) + footer = ControlFlowTemplate._indent_multiline_string(self.except_footer.to_indented_source(source_lines)) + except_lines.append(footer) + + return "\n".join(except_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsTemplate311.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsTemplate311.py new file mode 100644 index 0000000..a8a95a2 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptAsTemplate311.py @@ -0,0 +1,196 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ...abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate + + +from ...subtemplates.OptionalExitSubtemplate import ExitSubTemplate +from .ExceptAsNonMatchSubtemplate311 import ExceptAsNonMatchSubTemplate311 +from .ExceptAsCleanupSubTemplate311 import ExceptAsCleanupSubTemplate311 + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import optional_node, optional_edge, assert_in_degree, assert_except_as + + + +class ExceptAsTemplate311(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + """ + An `except as` block, after its cleanup has been structured. + If there are multiple, this will match the last block in the series and set up the next one to be matched + (0) + / \\j --> (0123) + (1) (2) |j + | (4) + (3) + |j + (4) + + 0,1,2 all have an exception edge to the panic cleanup from the current try block + """ + + _subgraph = { + "except_as_header": TemplateNode( + node_verification_func=assert_except_as, + natural_edge=TemplateEdge( + source="except_as_header", + dest="except_body", + ), + conditional_edge=TemplateEdge(source="except_as_header", dest="non_match_path"), + exception_edge=TemplateEdge( + source="except_as_header", + dest="panic_except", + ), + ), + "except_body": TemplateNode( + subtemplate=ExceptAsCleanupSubTemplate311, + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="except_body", + dest="except_footer", + ), + exception_edge=TemplateEdge( + source="except_body", + dest="panic_except", + ), + ), + "non_match_path": TemplateNode( + subtemplate=ExceptAsNonMatchSubTemplate311, + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="non_match_path", + dest="panic_except", + ), + natural_edge=TemplateEdge( + edge_verification_func=optional_edge, + source="non_match_path", + dest="after_except", + commit_none_to_mapping=False, + ), + ), + "except_footer": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="except_footer", + dest="after_except", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="except_footer", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "panic_except": TemplateNode( + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ) + ), + "after_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_as_header: ControlFlowTemplate, except_body: ControlFlowTemplate, except_footer: ControlFlowTemplate, non_match_path: ControlFlowTemplate): + self.except_as_header = except_as_header + self.except_body = except_body + self.except_footer = except_footer + self.non_match_path = non_match_path + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptAsTemplate311._subgraph, root_key="except_as_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + except_as_template = ExceptAsTemplate311(except_as_header=mapping["except_as_header"], except_body=mapping["except_body"], except_footer=mapping["except_footer"], non_match_path=mapping["non_match_path"]) + + in_edges = ((src, except_as_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + out_edges = [] + if mapping["panic_except"]: + out_edges.append((except_as_template, mapping["panic_except"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + if mapping.get("after_except", None): + out_edges.append((except_as_template, mapping["after_except"], {"type": ControlFlowEdgeType.JUMP.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([except_as_template.except_as_header, except_as_template.except_body, except_as_template.except_footer, except_as_template.non_match_path]) + reduced_cfg.add_node(except_as_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + except_lines = [] + + header = self.except_as_header.to_indented_source(source_lines) + except_lines.append(header) + + body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)) + except_lines.append(body) + + footer = ControlFlowTemplate._indent_multiline_string(self.except_footer.to_indented_source(source_lines)) + except_lines.append(footer) + + non_match = self.non_match_path.to_indented_source(source_lines) + except_lines.append(non_match) + + return "\n".join(except_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptTemplate311.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptTemplate311.py new file mode 100644 index 0000000..2262384 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/ExceptTemplate311.py @@ -0,0 +1,430 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ...abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate + +from ...natural.InstructionTemplate import InstructionTemplate + + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import ( + optional_node, + optional_edge, + assert_in_degree, + node_match_all, + assert_first_instruction_opname, + ends_with_opname_sequence, + is_exactly_opname, + node_match_any, +) + + + +class ExceptTemplate311(ControlFlowTemplate): + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + if isinstance(node, InstructionTemplate) and node.instruction.opname == "RERAISE": + return cfg + if isinstance(node, ExceptETemplate311): + return cfg + new_cfg = ExceptETemplate311.try_to_match_node(cfg, node) + if new_cfg is not None: + return new_cfg + new_cfg = BareExcept311.try_to_match_node(cfg, node) + if new_cfg is not None: + return new_cfg + + +class Footer(ControlFlowTemplate): + _subgraph = { + "swap": TemplateNode(node_verification_func=node_match_all(is_exactly_opname("SWAP"), assert_in_degree(1)), natural_edge=TemplateEdge(source="swap", dest="footer"), exception_edge=TemplateEdge(source="swap", dest="panic")), + "footer": TemplateNode( + natural_edge=TemplateEdge(source="footer", dest=None, edge_verification_func=optional_edge), + conditional_edge=TemplateEdge(source="footer", dest=None, edge_verification_func=optional_edge), + exception_edge=TemplateEdge(source="footer", dest=None, edge_verification_func=optional_edge), + ), + "panic": TemplateNode(node_verification_func=is_exactly_opname("COPY", "POP_EXCEPT", "RERAISE"), exception_edge=TemplateEdge(source="panic", dest=None, edge_verification_func=optional_edge)), + } + + def __init__(self, footer): + self.footer = footer + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=Footer._subgraph, root_key="swap", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return cfg + + template = Footer(mapping["footer"]) + + edges = [(next(cfg.predecessors(node)), template, {"type": ControlFlowEdgeType.NATURAL.value})] + edges.extend((template, dst, prop) for src, dst, prop in cfg.out_edges(mapping["footer"], data=True)) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from((node, mapping["footer"])) + reduced_cfg.add_node(template) + reduced_cfg.add_edges_from(edges) + return reduced_cfg + + def to_indented_source(self, source_lines): + return self.footer.to_indented_source(source_lines) + + +class ExceptBody(ControlFlowTemplate): + _subgraph = { + "store": TemplateNode( + node_verification_func=node_match_any(is_exactly_opname("STORE_FAST"), is_exactly_opname("STORE_NAME")), + natural_edge=TemplateEdge( + source="store", + dest="body", + ), + exception_edge=TemplateEdge( + source="store", + dest="panic", + ), + ), + "body": TemplateNode( + natural_edge=TemplateEdge( + source="body", + dest="footer", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="body", + dest="cleanup", + ), + ), + "cleanup": TemplateNode( + node_verification_func=node_match_any( + is_exactly_opname("LOAD_CONST", "STORE_FAST", "DELETE_FAST", "RERAISE"), + is_exactly_opname("LOAD_CONST", "STORE_NAME", "DELETE_NAME", "RERAISE"), + ), + exception_edge=TemplateEdge(source="cleanup", dest="panic"), + ), + "panic": TemplateNode(exception_edge=TemplateEdge(source="panic", dest=None, edge_verification_func=optional_edge)), + "footer": TemplateNode( + subtemplate=Footer, + node_verification_func=optional_node, + natural_edge=TemplateEdge(source="footer", dest=None, edge_verification_func=optional_edge), + conditional_edge=TemplateEdge(source="footer", dest=None, edge_verification_func=optional_edge), + exception_edge=TemplateEdge(source="footer", dest=None, edge_verification_func=optional_edge), + ), + } + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptBody._subgraph, root_key="store", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return cfg + + header = next(cfg.predecessors(node)) + footer = mapping.get("footer") + + template = ExceptBody(mapping["body"]) + edges = [(header, template, {"type": ControlFlowEdgeType.NATURAL.value}), (template, mapping["panic"], {"type": ControlFlowEdgeType.EXCEPTION.value})] + if footer: + edges.append((template, footer, {"type": ControlFlowEdgeType.NATURAL.value})) + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([mapping["store"], template.body, mapping["cleanup"]]) + reduced_cfg.add_node(template) + reduced_cfg.add_edges_from(edges) + return reduced_cfg + + def __init__(self, body): + self.body = body + + def to_indented_source(self, source_lines): + return self.body.to_indented_source(source_lines) + + +class BareExcept311(ControlFlowTemplate): + _subgraph = { + "body": TemplateNode( + natural_edge=TemplateEdge(source="body", dest="footer"), + exception_edge=TemplateEdge( + source="body", + dest="panic", + ), + ), + "footer": TemplateNode( + node_verification_func=node_match_all(assert_first_instruction_opname("POP_EXCEPT"), assert_in_degree(1)), + natural_edge=TemplateEdge( + source="footer", + dest="after_except", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="except_footer", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + ), + "panic": TemplateNode(node_verification_func=is_exactly_opname("COPY", "POP_EXCEPT", "RERAISE"), exception_edge=TemplateEdge(source="panic", dest="outer_exception_handler", edge_verification_func=optional_edge)), + "after_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + conditional_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + conditional_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + ), + } + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=BareExcept311._subgraph, root_key="body", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + template = BareExcept311( + body=mapping["body"], + footer=mapping["footer"], + ) + + in_edges = ((src, template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + out_edges = [] + if mapping["panic"]: + out_edges.append((template, mapping["panic"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + if mapping.get("after_except", None): + out_edges.append((template, mapping["after_except"], {"type": ControlFlowEdgeType.JUMP.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([template.body, template.footer]) + reduced_cfg.add_node(template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def __init__(self, body, footer): + self.body = body + self.footer = footer + + def to_indented_source(self, source_lines): + return "\n".join(["except:", self._indent_multiline_string(self.body.to_indented_source(source_lines)), self._indent_multiline_string(self.footer.to_indented_source(source_lines))]) + + +class ExceptETemplate311(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + _subgraph = { + "except_header": TemplateNode( + node_verification_func=node_match_any( + ends_with_opname_sequence("CHECK_EXC_MATCH", "POP_JUMP_FORWARD_IF_FALSE"), + ends_with_opname_sequence("CHECK_EXC_MATCH", "POP_JUMP_IF_FALSE"), + ), + natural_edge=TemplateEdge( + source="except_header", + dest="except_body", + ), + conditional_edge=TemplateEdge(source="except_header", dest="non_match_path"), + exception_edge=TemplateEdge( + source="except_header", + dest="panic_except", + ), + ), + "except_body": TemplateNode( + subtemplate=ExceptBody, + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="except_body", dest="except_footer", edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="except_body", + dest="panic_except", + ), + ), + "non_match_path": TemplateNode( + subtemplate=ExceptTemplate311, + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="non_match_path", + dest="panic_except", + ), + natural_edge=TemplateEdge( + edge_verification_func=optional_edge, + source="non_match_path", + dest="after_except", + commit_none_to_mapping=False, + ), + ), + "except_footer": TemplateNode( + node_verification_func=node_match_all(assert_first_instruction_opname("POP_EXCEPT"), assert_in_degree(1)), + natural_edge=TemplateEdge( + source="except_footer", + dest="after_except", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="except_footer", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "panic_except": TemplateNode( + node_verification_func=is_exactly_opname("COPY", "POP_EXCEPT", "RERAISE"), + exception_edge=TemplateEdge( + source="panic_except", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "after_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_except", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_header: ControlFlowTemplate, except_body: ControlFlowTemplate, except_footer: ControlFlowTemplate, non_match_path: ControlFlowTemplate): + self.except_header = except_header + self.except_body = except_body + self.except_footer = except_footer + self.non_match_path = non_match_path + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=ExceptETemplate311._subgraph, root_key="except_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + template = ExceptETemplate311(except_header=mapping["except_header"], except_body=mapping["except_body"], except_footer=mapping.get("except_footer"), non_match_path=mapping["non_match_path"]) + + in_edges = ((src, template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + out_edges = [] + if mapping["panic_except"]: + out_edges.append((template, mapping["panic_except"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + if mapping.get("after_except", None): + out_edges.append((template, mapping["after_except"], {"type": ControlFlowEdgeType.JUMP.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([template.except_header, template.except_body, template.non_match_path]) + if template.except_footer: + reduced_cfg.remove_node(template.except_footer) + reduced_cfg.add_node(template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + except_lines = [] + + header = self.except_header.to_indented_source(source_lines) + except_lines.append(header) + + body = ControlFlowTemplate._indent_multiline_string(self.except_body.to_indented_source(source_lines)) + except_lines.append(body) + + if self.except_footer: + footer = ControlFlowTemplate._indent_multiline_string(self.except_footer.to_indented_source(source_lines)) + except_lines.append(footer) + + non_match = self.non_match_path.to_indented_source(source_lines) + except_lines.append(non_match) + + return "\n".join(except_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/FinallyTemplate312.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/FinallyTemplate312.py new file mode 100644 index 0000000..6d5acc8 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/FinallyTemplate312.py @@ -0,0 +1,243 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...natural.InstructionTemplate import InstructionTemplate +from ...natural.LinearSequenceTemplate import LinearSequenceTemplate +from ...if_then.IfThenTemplate import IfThenTemplate +from ...if_then.IfElseTemplate import IfElseTemplate +from .TryTemplate311 import TryTemplate311 +from .TryTemplate312 import TryTemplate312 + + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import ( + assert_edge_type, + optional_node, + optional_edge, + assert_in_degree, + node_match_all, + assert_node_has_no_backwards_edges, + assert_instruction_opname, + is_exactly_opname, + assert_node_type, +) + + + +class FinallyTemplate312(ControlFlowTemplate): + _subgraph = { + "try_header": TemplateNode( + node_verification_func=assert_instruction_opname("NOP"), + natural_edge=TemplateEdge(source="try_header", dest="try_body", edge_verification_func=assert_edge_type(ControlFlowEdgeType.NATURAL)), + ), + "try_body": TemplateNode( + natural_edge=TemplateEdge(source="try_body", dest="finally_body", edge_verification_func=assert_edge_type(ControlFlowEdgeType.NATURAL)), + exception_edge=TemplateEdge( + source="try_body", + dest="fail", + ), + ), + "finally_body": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + ), + natural_edge=TemplateEdge(source="finally_body", dest=None, edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="finally_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "fail": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="except_body", + dest="panic_except", + edge_verification_func=optional_edge, + ), + ), + "panic_except": TemplateNode( + node_verification_func=is_exactly_opname("COPY", "POP_EXCEPT", "RERAISE"), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + _subgraph2 = { + "try_except": TemplateNode( + node_verification_func=assert_node_type(TryTemplate311, TryTemplate312), + natural_edge=TemplateEdge(source="try_except", dest="finally_body", edge_verification_func=assert_edge_type(ControlFlowEdgeType.NATURAL)), + exception_edge=TemplateEdge( + source="try_except", + dest="fail", + ), + ), + "finally_body": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + ), + natural_edge=TemplateEdge(source="finally_body", dest=None, edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="finally_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "fail": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="except_body", + dest="panic_except", + edge_verification_func=optional_edge, + ), + ), + "panic_except": TemplateNode( + node_verification_func=is_exactly_opname("COPY", "POP_EXCEPT", "RERAISE"), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, try_header: ControlFlowTemplate, try_body: ControlFlowTemplate, finally_body: ControlFlowTemplate, fail: ControlFlowTemplate, panic_except: ControlFlowTemplate, cutoff): + self.try_header = try_header + self.try_body = try_body + self.finally_body = finally_body + self.fail = fail + self.panic_except = panic_except + self.cutoff = cutoff + + @staticmethod + def mapping_verification_func(cfg, mapping): + finally_body = mapping["finally_body"] + fail = mapping["fail"] + if any(x.starts_line is not None for x in fail.get_instructions()): + return False + if not isinstance(finally_body, LinearSequenceTemplate): + finally_body = LinearSequenceTemplate(finally_body) + if not isinstance(fail, LinearSequenceTemplate): + fail = LinearSequenceTemplate(fail) + if isinstance(fail.members[0], InstructionTemplate) and fail.members[0].instruction.opname == "PUSH_EXC_INFO": + fail.members = fail.members[1:] + if isinstance(fail.members[-1], InstructionTemplate) and fail.members[-1].instruction.opname == "RERAISE": + fail.members = fail.members[:-1] + for x, y in zip(finally_body.members, fail.members): + if type(x) is not type(y) and not all(type(a) in [IfThenTemplate, IfElseTemplate] for a in (x, y)): + return False + mapping["cutoff"] = x + return True + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=FinallyTemplate312._subgraph, root_key="try_header", mapping_verification_func=FinallyTemplate312.mapping_verification_func) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + matcher = GraphTemplateMatcher(template_node_dict=FinallyTemplate312._subgraph2, root_key="try_except", mapping_verification_func=FinallyTemplate312.mapping_verification_func) + + mapping = matcher.match_at_graph_node(cfg, node) + if not mapping: + return None + mapping["try_header"] = None + mapping["try_body"] = mapping["try_except"] + + reduced_cfg = cfg.copy() + + # "bite off" the NOP from a linear sequence template + if isinstance(mapping["try_header"], LinearSequenceTemplate): + # grab the nop and update the linear sequence + nop_inst_template = mapping["try_header"].members[-1] + mapping["try_header"].members = mapping["try_header"].members[:-1] + if len(mapping["try_header"].members) == 1: + nx.relabel_nodes(reduced_cfg, {mapping["try_header"]: mapping["try_header"].members[0]}, copy=False) + mapping["try_header"] = mapping["try_header"].members[0] + + # transfer outgoing edges to the bitten off chunk + header_out_edges = list(reduced_cfg.out_edges(mapping["try_header"], data=True)) + reduced_cfg.add_node(nop_inst_template) + reduced_cfg.remove_edges_from(header_out_edges) + reduced_cfg.add_edges_from((nop_inst_template, dst, data) for src, dst, data in header_out_edges) + reduced_cfg.add_edge(mapping["try_header"], nop_inst_template, type=ControlFlowEdgeType.NATURAL.value) + mapping["try_header"] = nop_inst_template + + template = FinallyTemplate312(try_header=mapping["try_header"], try_body=mapping["try_body"], finally_body=mapping["finally_body"], fail=mapping["fail"], panic_except=mapping["panic_except"], cutoff=mapping["cutoff"]) + + in_edges = ((src, template, edge_properties) for src, dst, edge_properties in reduced_cfg.in_edges(template.try_header or template.try_body, data=True)) + out_edges = [(template, dst, edge_properties) for src, dst, edge_properties in reduced_cfg.out_edges(template.finally_body, data=True)] + if mapping["outer_exception_handler"]: + out_edges.append((template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg.remove_nodes_from([template.try_header, template.try_body, template.finally_body, template.fail, template.panic_except]) + reduced_cfg.add_node(template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + try_header = self.try_header.to_indented_source(source_lines) if self.try_header else "" + try_body = self._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + + if isinstance(self.finally_body, LinearSequenceTemplate): + i = self.finally_body.members.index(self.cutoff) + 1 + in_finally = self._indent_multiline_string(LinearSequenceTemplate(*self.finally_body.members[:i]).to_indented_source(source_lines)) + after = LinearSequenceTemplate(*self.finally_body.members[i:]).to_indented_source(source_lines) + else: + in_finally = self._indent_multiline_string(self.finally_body.to_indented_source(source_lines)) + after = "" + + lines = [try_header, "try:", try_body, "finally: # inserted", in_finally, after] + + return "\n".join(lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryExceptTemplate311.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryExceptTemplate311.py new file mode 100644 index 0000000..9c5f5b7 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryExceptTemplate311.py @@ -0,0 +1,213 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate +from ...natural.LinearSequenceTemplate import LinearSequenceTemplate +from ...loop.LoopExitTemplate import LoopExitTemplate + +from .ExceptAsNonMatchSubtemplate311 import ExceptAsNonMatchSubTemplate311 + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import assert_edge_type, optional_node, optional_edge, assert_in_degree, node_match_all, assert_node_has_no_backwards_edges, assert_instruction_opname +from ...subtemplates.OptionalExitSubtemplate import ExitSubTemplate + + + +class TryExceptTemplate311(ControlFlowTemplate): + """ + A `try-except` block with just a naked except in Python 3.11+. + (-1) + | (-1) + (0) | + / \\e --> (01235) + (1) (2) | + | | \\e (4) + | (3) (5) + \\j /j + (4) + One or more of the try/except may have no further control flow. + However, if both have successors, they must go to the same place. + """ + + _subgraph = { + "try_header": TemplateNode( + node_verification_func=assert_instruction_opname("NOP"), + natural_edge=TemplateEdge(source="try_header", dest="try_body", edge_verification_func=assert_edge_type(ControlFlowEdgeType.NATURAL)), + ), + "try_body": TemplateNode( + natural_edge=TemplateEdge(source="try_body", dest="try_footer", edge_verification_func=assert_edge_type(ControlFlowEdgeType.NATURAL)), + exception_edge=TemplateEdge( + source="try_body", + dest="except_body", + ), + ), + "try_footer": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + ), + natural_edge=TemplateEdge(source="try_footer", dest="after_try_except", edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="try_footer", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "except_body": TemplateNode( + subtemplate=ExceptAsNonMatchSubTemplate311, + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="except_body", dest="after_try_except", edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="except_body", + dest="panic_except", + edge_verification_func=optional_edge, + ), + ), + "panic_except": TemplateNode( + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ) + ), + "after_try_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_try_except", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, try_header: ControlFlowTemplate, try_body: ControlFlowTemplate, try_footer: ControlFlowTemplate, except_body: ControlFlowTemplate, panic_except: ControlFlowTemplate): + self.try_header = try_header + self.try_body = try_body + self.try_footer = try_footer + self.except_body = except_body + self.panic_except = panic_except + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=TryExceptTemplate311._subgraph, root_key="try_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + reduced_cfg: nx.DiGraph = cfg.copy() + # "bite off" the NOP from a linear sequence template + if isinstance(mapping["try_header"], LinearSequenceTemplate): + # grab the nop and update the linear sequence + nop_inst_template = mapping["try_header"].members[-1] + mapping["try_header"].members = mapping["try_header"].members[:-1] + if len(mapping["try_header"].members) == 1: + nx.relabel_nodes(reduced_cfg, {mapping["try_header"]: mapping["try_header"].members[0]}, copy=False) + mapping["try_header"] = mapping["try_header"].members[0] + + # transfer outgoing edges to the bitten off chunk + header_out_edges = list(reduced_cfg.out_edges(mapping["try_header"], data=True)) + reduced_cfg.add_node(nop_inst_template) + reduced_cfg.remove_edges_from(header_out_edges) + reduced_cfg.add_edges_from((nop_inst_template, dst, data) for src, dst, data in header_out_edges) + reduced_cfg.add_edge(mapping["try_header"], nop_inst_template, type=ControlFlowEdgeType.NATURAL.value) + mapping["try_header"] = nop_inst_template + + try_except_template = TryExceptTemplate311(try_header=mapping["try_header"], try_body=mapping["try_body"], try_footer=mapping.get("try_footer", None), except_body=mapping["except_body"], panic_except=mapping["panic_except"]) + + in_edges = ((src, try_except_template, edge_properties) for src, dst, edge_properties in reduced_cfg.in_edges(try_except_template.try_header, data=True)) + # only preserve exception handling edges + # insert a continuation edge to after the try except + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((try_except_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + if "after_try_except" in mapping.keys(): + after_try_except = mapping["after_try_except"] + out_edges.append((try_except_template, after_try_except, {"type": ControlFlowEdgeType.NATURAL.value})) + + reduced_cfg.remove_nodes_from([try_except_template.try_header, try_except_template.try_body, try_except_template.try_footer, try_except_template.except_body, try_except_template.panic_except]) + reduced_cfg.add_node(try_except_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + try_header = self.try_header.to_indented_source(source_lines) + try_body = ControlFlowTemplate._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + + # check if there is a try footer as in 3.7 there may not be a try footer at all + if self.try_footer: + try_footer = ControlFlowTemplate._indent_multiline_string(self.try_footer.to_indented_source(source_lines)) + else: + try_footer = "" + + try_except_lines = [try_header, "try:", try_body, try_footer] + + # if we matched against an "Except ... as" chain, then omit the inserted except: block + omit_except = False + if isinstance(self.except_body, AbstractExceptionBlockTemplate): + omit_except = True + elif isinstance(self.except_body, LoopExitTemplate): + if isinstance(self.except_body.tail, AbstractExceptionBlockTemplate): + omit_except = True + + except_body = self.except_body.to_indented_source(source_lines) + if not omit_except: + try_except_lines.append("except:") + except_body = ControlFlowTemplate._indent_multiline_string(except_body) + + try_except_lines.append(except_body) + + # the panic except should never have a line + + return "\n".join(try_except_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryTemplate311.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryTemplate311.py new file mode 100644 index 0000000..e7900ff --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryTemplate311.py @@ -0,0 +1,178 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...natural.LinearSequenceTemplate import LinearSequenceTemplate + +from .ExceptTemplate311 import ExceptTemplate311 + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import ( + assert_edge_type, + optional_node, + optional_edge, + assert_in_degree, + node_match_all, + assert_node_has_no_backwards_edges, + assert_instruction_opname, + is_exactly_opname, +) + + + +class TryTemplate311(ControlFlowTemplate): + _subgraph = { + "try_header": TemplateNode( + node_verification_func=assert_instruction_opname("NOP"), + natural_edge=TemplateEdge(source="try_header", dest="try_body", edge_verification_func=assert_edge_type(ControlFlowEdgeType.NATURAL)), + ), + "try_body": TemplateNode( + natural_edge=TemplateEdge(source="try_body", dest="try_footer", edge_verification_func=assert_edge_type(ControlFlowEdgeType.NATURAL)), + exception_edge=TemplateEdge( + source="try_body", + dest="except_body", + ), + ), + "try_footer": TemplateNode( + node_verification_func=node_match_all( + assert_in_degree(1), + assert_node_has_no_backwards_edges, + ), + natural_edge=TemplateEdge(source="try_footer", dest="after_try_except", edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="try_footer", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "except_body": TemplateNode( + subtemplate=ExceptTemplate311, + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="except_body", dest="after_try_except", edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="except_body", + dest="panic_except", + edge_verification_func=optional_edge, + ), + ), + "panic_except": TemplateNode( + node_verification_func=is_exactly_opname("COPY", "POP_EXCEPT", "RERAISE"), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "after_try_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_try_except", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, try_header: ControlFlowTemplate, try_body: ControlFlowTemplate, try_footer: ControlFlowTemplate, except_body: ControlFlowTemplate, panic_except: ControlFlowTemplate): + self.try_header = try_header + self.try_body = try_body + self.try_footer = try_footer + self.except_body = except_body + self.panic_except = panic_except + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=TryTemplate311._subgraph, root_key="try_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + reduced_cfg = cfg.copy() + + # "bite off" the NOP from a linear sequence template + if isinstance(mapping["try_header"], LinearSequenceTemplate): + # grab the nop and update the linear sequence + nop_inst_template = mapping["try_header"].members[-1] + mapping["try_header"].members = mapping["try_header"].members[:-1] + if len(mapping["try_header"].members) == 1: + nx.relabel_nodes(reduced_cfg, {mapping["try_header"]: mapping["try_header"].members[0]}, copy=False) + mapping["try_header"] = mapping["try_header"].members[0] + + # transfer outgoing edges to the bitten off chunk + header_out_edges = list(reduced_cfg.out_edges(mapping["try_header"], data=True)) + reduced_cfg.add_node(nop_inst_template) + reduced_cfg.remove_edges_from(header_out_edges) + reduced_cfg.add_edges_from((nop_inst_template, dst, data) for src, dst, data in header_out_edges) + reduced_cfg.add_edge(mapping["try_header"], nop_inst_template, type=ControlFlowEdgeType.NATURAL.value) + mapping["try_header"] = nop_inst_template + + try_except_template = TryTemplate311(try_header=mapping["try_header"], try_body=mapping["try_body"], try_footer=mapping["try_footer"], except_body=mapping["except_body"], panic_except=mapping["panic_except"]) + + in_edges = ((src, try_except_template, edge_properties) for src, dst, edge_properties in reduced_cfg.in_edges(try_except_template.try_header, data=True)) + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((try_except_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + if "after_try_except" in mapping.keys(): + after_try_except = mapping["after_try_except"] + out_edges.append((try_except_template, after_try_except, {"type": ControlFlowEdgeType.NATURAL.value})) + + reduced_cfg.remove_nodes_from([try_except_template.try_header, try_except_template.try_body, try_except_template.try_footer, try_except_template.except_body, try_except_template.panic_except]) + reduced_cfg.add_node(try_except_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + try_header = self.try_header.to_indented_source(source_lines) + try_body = self._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + + except_body = self.except_body.to_indented_source(source_lines) + + lines = [try_header, "try:", try_body, except_body] + + try_footer = self.try_footer.to_indented_source(source_lines) + if try_footer.strip(): + lines.extend(["else: # inserted", self._indent_multiline_string(try_footer)]) + + return "\n".join(lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryTemplate312.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryTemplate312.py new file mode 100644 index 0000000..9dc6cfc --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/post_311/TryTemplate312.py @@ -0,0 +1,169 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...natural.LinearSequenceTemplate import LinearSequenceTemplate + +from .ExceptTemplate311 import ExceptTemplate311 + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import ( + assert_edge_type, + optional_node, + optional_edge, + assert_in_degree, + assert_instruction_opname, + is_exactly_opname, +) + + + +class TryTemplate312(ControlFlowTemplate): + _subgraph = { + "try_header": TemplateNode( + node_verification_func=assert_instruction_opname("NOP"), + natural_edge=TemplateEdge(source="try_header", dest="try_body", edge_verification_func=assert_edge_type(ControlFlowEdgeType.NATURAL)), + ), + "try_body": TemplateNode( + natural_edge=TemplateEdge( + source="try_body", + dest="after_try_except", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="try_body", + dest="except_body", + ), + ), + "except_body": TemplateNode( + subtemplate=ExceptTemplate311, + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="except_body", dest="after_try_except", edge_verification_func=optional_edge, commit_none_to_mapping=False), + exception_edge=TemplateEdge( + source="except_body", + dest="panic_except", + edge_verification_func=optional_edge, + ), + ), + "panic_except": TemplateNode( + node_verification_func=is_exactly_opname("COPY", "POP_EXCEPT", "RERAISE"), + exception_edge=TemplateEdge( + source="except_body", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "after_try_except": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="after_try_except", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="after_try_except", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, try_header: ControlFlowTemplate, try_body: ControlFlowTemplate, except_body: ControlFlowTemplate, panic_except: ControlFlowTemplate): + self.try_header = try_header + self.try_body = try_body + self.except_body = except_body + self.panic_except = panic_except + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=TryTemplate312._subgraph, root_key="try_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + reduced_cfg = cfg.copy() + + # "bite off" the NOP from a linear sequence template + if isinstance(mapping["try_header"], LinearSequenceTemplate): + # grab the nop and update the linear sequence + nop_inst_template = mapping["try_header"].members[-1] + mapping["try_header"].members = mapping["try_header"].members[:-1] + if len(mapping["try_header"].members) == 1: + nx.relabel_nodes(reduced_cfg, {mapping["try_header"]: mapping["try_header"].members[0]}, copy=False) + mapping["try_header"] = mapping["try_header"].members[0] + + # transfer outgoing edges to the bitten off chunk + header_out_edges = list(reduced_cfg.out_edges(mapping["try_header"], data=True)) + reduced_cfg.add_node(nop_inst_template) + reduced_cfg.remove_edges_from(header_out_edges) + reduced_cfg.add_edges_from((nop_inst_template, dst, data) for src, dst, data in header_out_edges) + reduced_cfg.add_edge(mapping["try_header"], nop_inst_template, type=ControlFlowEdgeType.NATURAL.value) + mapping["try_header"] = nop_inst_template + + try_except_template = TryTemplate312( + try_header=mapping["try_header"], + try_body=mapping["try_body"], + except_body=mapping["except_body"], + panic_except=mapping["panic_except"], + ) + + in_edges = ((src, try_except_template, edge_properties) for src, dst, edge_properties in reduced_cfg.in_edges(try_except_template.try_header, data=True)) + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((try_except_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + if "after_try_except" in mapping.keys(): + after_try_except = mapping["after_try_except"] + out_edges.append((try_except_template, after_try_except, {"type": ControlFlowEdgeType.NATURAL.value})) + + reduced_cfg.remove_nodes_from([try_except_template.try_header, try_except_template.try_body, try_except_template.except_body, try_except_template.panic_except]) + reduced_cfg.add_node(try_except_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + try_header = self.try_header.to_indented_source(source_lines) + try_body = self._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + + except_body = self.except_body.to_indented_source(source_lines) + + lines = [try_header, "try:", try_body, except_body] + + return "\n".join(lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/ExceptAsPre39.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/ExceptAsPre39.py new file mode 100644 index 0000000..2988614 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/ExceptAsPre39.py @@ -0,0 +1,165 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable +from ...abstract.AbstractExceptionBlockTemplate import AbstractExceptionBlockTemplate +from ...natural.LinearSequenceTemplate import LinearSequenceTemplate +from ...try_except.pre_39.TryFinallyPre39 import Pre39TryFinallyTemplate + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import optional_node, optional_edge, assert_in_degree, assert_except_as + + + +class Pre39ExceptAsTemplate(ControlFlowTemplate, AbstractNonSequentiable, AbstractExceptionBlockTemplate): + """ + An `except as` block, after its cleanup has been structured. + If there are multiple, this will match the last block in the series and set up the next one to be matched + (0) + / \\j --> (012) + (1) (2) |j + |j (3) + (3) + """ + + _subgraph = { + "except_as_header": TemplateNode( + node_verification_func=assert_except_as, + natural_edge=TemplateEdge( + source="except_as_header", + dest="except_setup", + ), + conditional_edge=TemplateEdge(source="except_as_header", dest="non_match_path"), + exception_edge=TemplateEdge(source="except_as_header", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "except_setup": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="except_setup", + dest="except_body", + ), + exception_edge=TemplateEdge(source="except_setup", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "except_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge(source="except_body", dest="begin_finally", edge_verification_func=optional_edge), + exception_edge=TemplateEdge( + source="except_body", + dest="cleanup", + ), + ), + "begin_finally": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="begin_finally", + dest="cleanup", + ), + exception_edge=TemplateEdge(source="begin_finally", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "cleanup": TemplateNode( + exception_edge=TemplateEdge( + source="cleanup", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "non_match_path": TemplateNode( + node_verification_func=assert_in_degree(1), + exception_edge=TemplateEdge( + source="non_match_path", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, except_as_header: ControlFlowTemplate, except_setup: ControlFlowTemplate, except_body: ControlFlowTemplate, begin_finally: ControlFlowTemplate, cleanup: ControlFlowTemplate, non_match_path: ControlFlowTemplate): + self.except_as_header = except_as_header + self.except_setup = except_setup + self.except_body = except_body + self.begin_finally = begin_finally + self.cleanup = cleanup + self.non_match_path = non_match_path + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + matcher = GraphTemplateMatcher(template_node_dict=Pre39ExceptAsTemplate._subgraph, root_key="except_as_header", mapping_verification_func=None) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + except_as_cleanup_template = Pre39ExceptAsTemplate( + except_as_header=mapping["except_as_header"], except_setup=mapping["except_setup"], except_body=mapping["except_body"], begin_finally=mapping["begin_finally"], cleanup=mapping["cleanup"], non_match_path=mapping["non_match_path"] + ) + + in_edges = ((src, except_as_cleanup_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(node, data=True)) + # only preserve exception handling edges + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((except_as_cleanup_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from( + [ + except_as_cleanup_template.except_as_header, + except_as_cleanup_template.except_setup, + except_as_cleanup_template.except_body, + except_as_cleanup_template.begin_finally, + except_as_cleanup_template.cleanup, + except_as_cleanup_template.non_match_path, + ] + ) + reduced_cfg.add_node(except_as_cleanup_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + header = self.except_as_header.to_indented_source(source_lines) + if isinstance(self.except_body, LinearSequenceTemplate): + assert isinstance(self.except_body[0], Pre39TryFinallyTemplate) + _body = self.except_body[0].try_body.to_indented_source(source_lines) + else: + assert isinstance(self.except_body, Pre39TryFinallyTemplate) + _body = self.except_body.try_body.to_indented_source(source_lines) + body = ControlFlowTemplate._indent_multiline_string(_body) + non_match = self.non_match_path.to_indented_source(source_lines) + return f"{header}\n{body}\n{non_match}" + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/TryFinallyExitPre39.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/TryFinallyExitPre39.py new file mode 100644 index 0000000..bc2da20 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/TryFinallyExitPre39.py @@ -0,0 +1,211 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import optional_node, optional_edge, assert_in_degree, assert_instruction_opname, node_match_none, node_match_all, contains_opname_sequence + +from ...subtemplates.OptionalExitSubtemplate import ExitSubTemplate + + +class Pre39TryFinallyExitTemplate(ControlFlowTemplate, AbstractNonSequentiable): + r""" + A `try` block with only a `finally` following it. But 3.8 and below. This has a similar structure to the with template. + (0) only here because could not figure out a way to condense an exit without killing off the tail + | + (1) + / e\ --> (0123) + (2) \ | + \ / (4) + (3) + does not cover additional finally blocks that will be inserted in the bytecode as a result of returns / breaking out of loops + """ + + _subgraph = { + "setup_finally": TemplateNode( + node_verification_func=node_match_all( + assert_instruction_opname("SETUP_FINALLY"), + node_match_none( + contains_opname_sequence( + "POP_TOP", + "STORE_FAST", + "POP_TOP", + ), + ), + ), + natural_edge=TemplateEdge( + source="setup_finally", + dest="try_body", + ), + exception_edge=TemplateEdge(source="setup_finally", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "try_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="try_body", + dest="begin_finally", + ), + exception_edge=TemplateEdge( + source="try_body", + dest="finally", + ), + ), + "begin_finally": TemplateNode( + subtemplate=ExitSubTemplate, + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="begin_finally", + dest="finally", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="begin_finally", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "finally": TemplateNode( + subtemplate=ExitSubTemplate, + natural_edge=TemplateEdge( + source="finally", + dest="tail", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="finally", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__( + self, + setup_finally: ControlFlowTemplate, + try_body: ControlFlowTemplate, + begin_finally: ControlFlowTemplate, + _finally: ControlFlowTemplate, + ): + self.setup_finally = setup_finally + self.try_body = try_body + self.begin_finally = begin_finally + self._finally = _finally + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + if cfg.in_degree(node) != 1: + return None + + # to avoid being treated as a try-except, we actually need to greedily search up one layer + node = next(cfg.predecessors(node)) + + matcher = GraphTemplateMatcher( + template_node_dict=Pre39TryFinallyExitTemplate._subgraph, + root_key="setup_finally", + mapping_verification_func=None, + ) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + finally_template = Pre39TryFinallyExitTemplate( + setup_finally=mapping["setup_finally"], + try_body=mapping["try_body"], + begin_finally=mapping["begin_finally"], + _finally=mapping["finally"], + ) + + in_edges = [(src, finally_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(finally_template.setup_finally, data=True)] + # only preserve exception handling edges + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((finally_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + # if there is a tail add a natural out edge + if mapping.get("tail", None): + out_edges.append((finally_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from( + [ + finally_template.setup_finally, + finally_template.try_body, + finally_template.begin_finally, + finally_template._finally, + ] + ) + reduced_cfg.add_node(finally_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + # sometimes the setup finally is included in a linear sequence, so we need to include that source + setup_finally = self.setup_finally.to_indented_source(source_lines) + try_block = ControlFlowTemplate._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + # pick one of the finally bodies to get the source code from + finally_body = ControlFlowTemplate._indent_multiline_string(self._finally.to_indented_source(source_lines)) + + if not finally_body: + finally_body = ControlFlowTemplate._indent_multiline_string(self._finally.to_indented_source(source_lines)) + finally_lines = [setup_finally, "try:", try_block, "finally:", finally_body] + return "\n".join(finally_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/TryFinallyPre39.py b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/TryFinallyPre39.py new file mode 100644 index 0000000..17d0107 --- /dev/null +++ b/pylingual/control_flow_reconstruction/control_flow_templates/try_except/pre_39/TryFinallyPre39.py @@ -0,0 +1,188 @@ +import networkx as nx + +import itertools + +from ...abstract.AbstractTemplate import ControlFlowTemplate +from ...abstract.AbstractNonSequentiableTemplate import AbstractNonSequentiable + +from ....cfg_utils import ControlFlowEdgeType + +from ...Subgraph import TemplateEdge, TemplateNode, GraphTemplateMatcher + +from ...match_utils import optional_node, optional_edge, assert_in_degree, assert_instruction_opname + + + +class Pre39TryFinallyTemplate(ControlFlowTemplate, AbstractNonSequentiable): + r""" + A `try` block with only a `finally` following it. But 3.8 and below. This has a similar structure to the with template + (0) + | + (1) + / e\ --> (0123) + (2) \ | + \ / (4) + (3) + does not cover additional finally blocks that will be inserted in the bytecode as a result of returns / breaking out of loops + """ + + _subgraph = { + "setup_finally": TemplateNode( + node_verification_func=assert_instruction_opname("SETUP_FINALLY"), + natural_edge=TemplateEdge( + source="setup_finally", + dest="try_body", + ), + exception_edge=TemplateEdge(source="setup_finally", dest="outer_exception_handler", edge_verification_func=optional_edge), + ), + "try_body": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="try_body", + dest="begin_finally", + ), + exception_edge=TemplateEdge( + source="try_body", + dest="finally", + ), + ), + "begin_finally": TemplateNode( + node_verification_func=assert_in_degree(1), + natural_edge=TemplateEdge( + source="begin_finally", + dest="finally", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="begin_finally", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "finally": TemplateNode( + natural_edge=TemplateEdge( + source="finally", + dest="tail", + edge_verification_func=optional_edge, + commit_none_to_mapping=False, + ), + exception_edge=TemplateEdge( + source="finally", + dest="outer_exception_handler", + edge_verification_func=optional_edge, + ), + ), + "tail": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="tail", + dest=None, + edge_verification_func=optional_edge, + ), + ), + "outer_exception_handler": TemplateNode( + node_verification_func=optional_node, + natural_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + exception_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + conditional_edge=TemplateEdge( + source="outer_exception_handler", + dest=None, + edge_verification_func=optional_edge, + ), + ), + } + + def __init__(self, setup_finally: ControlFlowTemplate, try_body: ControlFlowTemplate, begin_finally: ControlFlowTemplate, _finally: ControlFlowTemplate): + self.setup_finally = setup_finally + self.try_body = try_body + self.begin_finally = begin_finally + self._finally = _finally + + @staticmethod + def try_to_match_node(cfg: nx.DiGraph, node) -> nx.DiGraph: + """ + Attempts to match this template on the graph at the given node. + If successful, returns an updated cfg with the appropriate nodes condensed into an instance of this template. + Otherwise, returns None. + """ + if node not in cfg.nodes: + return None + + if cfg.in_degree(node) != 1: + return None + + # to avoid being treated as a try-except, we actually need to greedily search up one layer + node = next(cfg.predecessors(node)) + + matcher = GraphTemplateMatcher( + template_node_dict=Pre39TryFinallyTemplate._subgraph, + root_key="setup_finally", + mapping_verification_func=None, + ) + + mapping = matcher.match_at_graph_node(cfg, node) + + if not mapping: + return None + + finally_template = Pre39TryFinallyTemplate( + setup_finally=mapping["setup_finally"], + try_body=mapping["try_body"], + begin_finally=mapping["begin_finally"], + _finally=mapping["finally"], + ) + + in_edges = [(src, finally_template, edge_properties) for src, dst, edge_properties in cfg.in_edges(finally_template.setup_finally, data=True)] + + # only preserve exception handling edges + + out_edges = [] + if mapping["outer_exception_handler"]: + out_edges.append((finally_template, mapping["outer_exception_handler"], {"type": ControlFlowEdgeType.EXCEPTION.value})) + + # if there is a tail add it as an out edge + if mapping.get("tail", None): + out_edges.append((finally_template, mapping["tail"], {"type": ControlFlowEdgeType.NATURAL.value})) + + reduced_cfg = cfg.copy() + reduced_cfg.remove_nodes_from([finally_template.setup_finally, finally_template.try_body, finally_template.begin_finally, finally_template._finally]) + reduced_cfg.add_node(finally_template) + reduced_cfg.add_edges_from(itertools.chain(in_edges, out_edges)) + return reduced_cfg + + def to_indented_source(self, source_lines: list[str]) -> str: + """ + Returns the source code for this template, recursively calling into its children to create the full source code. + """ + # sometimes the setup finally is included in a linear sequence, so we need to include that source + setup_finally = self.setup_finally.to_indented_source(source_lines) + try_block = ControlFlowTemplate._indent_multiline_string(self.try_body.to_indented_source(source_lines)) + # pick one of the finally bodies to get the source code from + finally_body = ControlFlowTemplate._indent_multiline_string(self._finally.to_indented_source(source_lines)) + + if not finally_body: + finally_body = ControlFlowTemplate._indent_multiline_string(self._finally.to_indented_source(source_lines)) + finally_lines = [setup_finally, "try:", try_block, "finally:", finally_body] + return "\n".join(finally_lines) + + def __repr__(self) -> str: + return super().__repr__() diff --git a/pylingual/control_flow_reconstruction/reconstruct_control_indentation.py b/pylingual/control_flow_reconstruction/reconstruct_control_indentation.py new file mode 100644 index 0000000..6a48e06 --- /dev/null +++ b/pylingual/control_flow_reconstruction/reconstruct_control_indentation.py @@ -0,0 +1,114 @@ +from typing import Hashable + + +def postprocess(source_lines: list[str]): + i = 0 + tab = " " * 4 + decs = [] + while i < len(source_lines): + line = source_lines[i] + if line.startswith("global ") or line.startswith("nonlocal "): + decs.append(i) + elif line.startswith("__doc__ = "): + source_lines[i] = line[10:] + # should check for 'from __future__ import ', but lines are still masked now + # checking only for 'from ' doesn't make a difference though + elif not line.startswith('"""') and not line.startswith("from "): + break + i += 1 + for dec in reversed(decs): + source_lines.insert(i - 1, source_lines.pop(dec)) + block_level = None + can_have_return = [False] + + while i < len(source_lines): + line = source_lines[i] + tabs = len(line) - len(line.lstrip("\t")) + inserted = line.endswith("# inserted") + if inserted: + line = line[:-10] + line = line.strip() + while len(can_have_return) - 1 > tabs: + can_have_return.pop() + if line.startswith("def ") or line.startswith("class ") or line.startswith("async def "): + while len(can_have_return) - 1 < tabs + 1: + can_have_return.append(can_have_return[-1]) + can_have_return[tabs + 1] = not line.startswith("class") + # add newline between function and class defs + if line.startswith("@") or line.startswith("def ") or line.startswith("class ") or line.startswith("async def "): + if i and not source_lines[i - 1].strip().startswith("@") and block_level is None: + source_lines.insert(i, "") + i += 1 + if (line.startswith("return ") or line == "return") and not can_have_return[min(tabs, len(can_have_return) - 1)]: + source_lines.pop(i) + continue + # insert pass in empty blocks + if block_level is not None: + if tabs <= block_level or not line.strip(): + if source_lines[i - 1].strip().startswith("while True:"): + prev_line = source_lines[i - 1] + source_lines[i - 1] = " " * (len(prev_line) - len(prev_line.lstrip(" "))) + "pass" + else: + source_lines.insert(i, tab * (block_level + 1) + "pass # postinserted") + i += 1 + block_level = None + if line.endswith(":"): + block_level = tabs + + # convert tabs to spaces + source_lines[i] = tab * tabs + line + (" # inserted" if inserted else "") + i += 1 + if block_level is not None: + source_lines.insert(i, tab * (block_level + 1) + "pass # postinserted") + i += 1 + return "\n".join(source_lines) + + +def reconstruct_source(pyc, sources): + merged_source, blame = merge_indented_sources(pyc, sources) + return postprocess(merged_source), blame + + +def split_newlines(li): + return "\n".join(li).split("\n") + + +def indent_newlines(li, n=1): + li = [line for line in split_newlines(li)] + return ["\t" * n + line for line in li] + + +def merge_indented_sources(pyc, sources): + blame_dict = {} + for bytecode in pyc.child_bytecodes: + sources[bytecode.codeobj], blame_dict[bytecode.codeobj] = merge_indented_sources(bytecode, sources) + line = 0 + indented_source = split_newlines(sources[pyc.codeobj]) + blame = [pyc.codeobj] * len(indented_source) + lines_set = set() + for i, instruction in enumerate(pyc.ordered_instructions): + if instruction.starts_line and instruction.starts_line not in lines_set: + lines_set.add(instruction.starts_line) + # count implicit else/finally/while True + while line < len(indented_source) and indented_source[line].endswith("# inserted"): + line += 1 + line += 1 + if instruction.opname == "LOAD_CONST" and isinstance(instruction.argval, Hashable): + if instruction.argval in sources: + if instruction.argval.co_name not in ("", "", "", "", ""): + new_tabs = 1 + + # add indentation of previous line + prev_line = "" + if line > 0: + if line < len(indented_source): + prev_line = indented_source[line - 1] + else: + prev_line = indented_source[-1] + new_tabs += len(prev_line) - len(prev_line.lstrip("\t")) + + code_to_insert = indent_newlines(sources[instruction.argval], new_tabs) + indented_source[line:line] = code_to_insert + blame[line:line] = blame_dict[instruction.argval] + line += len(code_to_insert) + return indented_source, blame diff --git a/pylingual/control_flow_reconstruction/structure_control_flow.py b/pylingual/control_flow_reconstruction/structure_control_flow.py new file mode 100644 index 0000000..e39f704 --- /dev/null +++ b/pylingual/control_flow_reconstruction/structure_control_flow.py @@ -0,0 +1,452 @@ +import networkx as nx + +import os + +from pylingual.utils.lazy import lazy_import + +from .cfg_utils import ControlFlowEdgeType, get_out_edge_dict, get_dominator_function +from pylingual.editable_bytecode import Inst +from pylingual.editable_bytecode import EditableBytecode + +# abstract type +from .control_flow_templates.abstract.AbstractTemplate import ControlFlowTemplate + +from .control_flow_templates.placeholders.IrreduciblePlaceholderTemplate import IrreduciblePlaceholderTemplate + +# default flow +from .control_flow_templates.natural.InstructionTemplate import InstructionTemplate +from .control_flow_templates.natural.LinearSequenceTemplate import LinearSequenceTemplate +from .control_flow_templates.natural.LineTemplate import LineTemplate + +# if/else +from .control_flow_templates.if_then.IfThenTemplate import IfThenTemplate +from .control_flow_templates.if_then.IfElseTemplate import IfElseTemplate +from .control_flow_templates.if_then.IfThenJumpTemplate import IfThenJumpTemplate +from .control_flow_templates.if_then.ConditionalExitTemplate import ConditionalExitTemplate +from .control_flow_templates.booleans.ShortCircuitOrTemplate import ShortCircuitOrTemplate +from .control_flow_templates.booleans.ShortCircuitOrContinueTemplate import ShortCircuitOrContinueTemplate +from .control_flow_templates.booleans.ShortCircuitAndTemplate import ShortCircuitAndTemplate +from .control_flow_templates.booleans.ChainedComparisonTemplate import ChainedComparisonTemplate +from .control_flow_templates.context_managers.WithTemplate import WithTemplate +from .control_flow_templates.context_managers.WithTemplate39 import WithTemplate39 +from .control_flow_templates.context_managers.WithCleanup312 import WithCleanup312 +from .control_flow_templates.context_managers.AsyncWithCleanup312 import AsyncWithCleanup312 +from .control_flow_templates.context_managers.WithTemplate312 import WithTemplate312 +from .control_flow_templates.context_managers.Await312Template import Await312Template + +# loops +from .control_flow_templates.loop.LoopTemplate import LoopTemplate +from .control_flow_templates.loop.SelfLoopTemplate import SelfLoopTemplate +from .control_flow_templates.loop.LoopExitTemplate import LoopExitTemplate +from .control_flow_templates.loop.PreRefinedLoopTemplate import PreRefinedLoopTemplate +from .control_flow_templates.loop.RefinedLoopTemplate import RefinedLoopTemplate +from .control_flow_templates.loop.WhileTrueIfElseTemplate import WhileTrueIfElseTemplate +from .control_flow_templates.loop.AsyncForTemplate import AsyncForTemplate +from .control_flow_templates.loop.InlinedComprehension import InlinedComprehensionTemplate +from .control_flow_templates.loop.ForIf312Template import ForIf312Template + +# exceptions +from .control_flow_templates.try_except.TryExceptTemplate import TryExceptTemplate +from .control_flow_templates.try_except.TryExceptElseTemplate import TryExceptElseTemplate +from .control_flow_templates.try_except.ExceptAsExceptTemplate import ExceptAsExceptTemplate +from .control_flow_templates.try_except.ExceptAsCleanup import ExceptAsCleanupTemplate +from .control_flow_templates.try_except.ExceptAsExitTemplate import ExceptAsExitTemplate +from .control_flow_templates.try_except.FinallyTemplate import FinallyTemplate +from .control_flow_templates.try_except.TryFinallyTemplate import TryFinallyTemplate +from .control_flow_templates.try_except.pre_39.TryFinallyPre39 import Pre39TryFinallyTemplate +from .control_flow_templates.try_except.pre_39.TryFinallyExitPre39 import Pre39TryFinallyExitTemplate +from .control_flow_templates.try_except.pre_39.ExceptAsPre39 import Pre39ExceptAsTemplate +from .control_flow_templates.try_except.ExceptException import ExceptException +from .control_flow_templates.try_except.GeneratorCleanupTemplate import GeneratorCleanupTemplate + +# 3.11/3.12-specific exceptions +from .control_flow_templates.try_except.post_311.TryTemplate311 import TryTemplate311 +from .control_flow_templates.try_except.post_311.TryTemplate312 import TryTemplate312 +from .control_flow_templates.try_except.post_311.FinallyTemplate312 import FinallyTemplate312 + +import pathlib + +lazy_import("pydot") + +from typing import Generator, Any + + +def viz(graph, name, node_label="label"): + namepath = pathlib.Path(name) + dot = pydot.Dot(namepath.name) + nodes = {} + + for node, data in graph.nodes.data(): + n = pydot.Node(hash(node), label=data[node_label]) + dot.add_node(n) + nodes[hash(node)] = n + + for node1, node2, data in graph.edges.data(): + edge = pydot.Edge(nodes[hash(node1)], nodes[hash(node2)], **data) + dot.add_edge(edge) + + try: + dot.write_png(name) + except FileNotFoundError: + dot.write_raw(name.replace(".png", ".dot")) + + +# order matters! +# More specific templates should appear before more general templates for correctness +# More common templates should appear before more rare templates for efficiency +cyclic_templates: list[type[ControlFlowTemplate]] = [ + WhileTrueIfElseTemplate, + LoopTemplate, + SelfLoopTemplate, + ShortCircuitOrTemplate, # the short circuit templates aren't cyclic, but are needed to match certain while loops + ShortCircuitAndTemplate, +] + +# priority dict structure +# Template type : (pass number, priority number) # lower is earlier +acyclic_templates_priority_dict: dict[ControlFlowTemplate, tuple[int, int]] = { + RefinedLoopTemplate: (0, 0), + AsyncForTemplate: (0, 1), # technically a cyclic template, but it searches up one node to complete the loop + FinallyTemplate: (0, 10), + WithTemplate: (0, 11), + LinearSequenceTemplate: (0, 20), + ExceptAsExitTemplate: (0, 25), + ExceptAsExceptTemplate: (0, 27), + ShortCircuitOrContinueTemplate: (0, 30), + IfElseTemplate: (1, 43), + IfThenTemplate: (1, 44), + IfThenJumpTemplate: (0, 45), + ConditionalExitTemplate: (0, 46), + ExceptAsCleanupTemplate: (0, 50), + TryFinallyTemplate: (0, 60), + TryExceptElseTemplate: (0, 62), + ShortCircuitOrTemplate: (0, 70), + ShortCircuitAndTemplate: (0, 71), + ChainedComparisonTemplate: (0, 72), +} + +# dictionary structure +# version: {template: (pass, priority)} +version_specific_acyclic_templates_dict: dict[tuple[int, int], dict[ControlFlowTemplate, tuple[int, int]]] = { + (3, 13): { + TryTemplate312: (-1, 60), + TryTemplate311: (-1, 61), + WithCleanup312: (-1, 0), + AsyncWithCleanup312: (-1, 0), + WithTemplate312: (0, 10), + InlinedComprehensionTemplate: (-1, 0), + GeneratorCleanupTemplate: (0, 1), + Await312Template: (0, 2), + ForIf312Template: (0, 0), + FinallyTemplate312: (-1, 199), + }, + (3, 12): { + TryTemplate312: (-1, 60), + TryTemplate311: (-1, 61), + WithCleanup312: (-1, 0), + AsyncWithCleanup312: (-1, 0), + WithTemplate312: (0, 10), + InlinedComprehensionTemplate: (-1, 0), + GeneratorCleanupTemplate: (0, 1), + Await312Template: (0, 2), + ForIf312Template: (0, 0), + FinallyTemplate312: (-1, 199), + }, + (3, 11): { + TryTemplate311: (-1, 55), + }, + (3, 9): { + WithTemplate39: (0, 12), + TryExceptTemplate: (1, 61), + }, + (3, 8): { + Pre39ExceptAsTemplate: (0, 40), + Pre39TryFinallyTemplate: (0, 60), + Pre39TryFinallyExitTemplate: (0, 75), + ExceptException: (0, 38), + TryExceptTemplate: (1, 61), + }, + (3, 7): { + Pre39ExceptAsTemplate: (0, 40), + Pre39TryFinallyTemplate: (0, 60), + Pre39TryFinallyExitTemplate: (0, 75), + ExceptException: (0, 38), + TryExceptTemplate: (1, 61), + }, + (3, 6): { + Pre39ExceptAsTemplate: (0, 40), + Pre39TryFinallyTemplate: (0, 60), + Pre39TryFinallyExitTemplate: (0, 75), + ExceptException: (0, 38), + TryExceptTemplate: (1, 61), + }, +} + + +def get_acyclic_template_passes(version: tuple[int, int]) -> Generator[list[ControlFlowTemplate], None, None]: + pass_dict = dict() + # accumulate the passes, merging in version-specific templates + for template, (pass_number, priority) in (acyclic_templates_priority_dict | version_specific_acyclic_templates_dict.get(version, dict())).items(): + pass_list = pass_dict.get(pass_number, list()) + pass_list.append((template, priority)) + pass_dict[pass_number] = pass_list + # sort each pass by priority + for pass_number, pass_list in pass_dict.items(): + pass_dict[pass_number] = [template for template, priority in sorted(pass_list, key=lambda item: item[1])] + # yield the templates for each pass + for pass_number in sorted(pass_dict.keys()): + yield pass_dict[pass_number] + + +def visualize(graph: nx.DiGraph, name, suffix): + # visualization is slow + if os.environ.get("DEBUG_CFLOW", None) != "1": + return + for n in graph.nodes: + graph.nodes[n]["label"] = repr(n) + v = next(x for x in graph.nodes if not isinstance(x, str)).get_instructions()[0].bytecode.version + viz(graph, f"/tmp/graph/{name}_{v[1]}_{suffix}.png", edge_label="type") + + +def structure_loop(cfg: nx.DiGraph, node) -> nx.DiGraph: + dominates = get_dominator_function(cfg) + # a node is a loop header if there are back-edges to it + # a latching node is a node with a back-edge to the loop header + # a back-edge is an edge from any node that is dominated by this node + latching_nodes = [pred for pred in cfg.predecessors(node) if dominates(node, pred)] + if not latching_nodes: + return None + + # attempt to match a loop template + for template in cyclic_templates: + candidate_cfg = template.try_to_match_node(cfg, node) + if candidate_cfg is not None: + return candidate_cfg + + if len(node.get_instructions()) == 1 and node.get_instructions()[0].opname == "SEND": + return None + + # identify the canonical loop exit and outer exception handler by looking at the loop header + loop_header_edge_dict = get_out_edge_dict(cfg, node) + canonical_loop_exit, _ = loop_header_edge_dict["conditional"] + outer_exception_handler, _ = loop_header_edge_dict["exception"] + + # subgraph containing all nodes dominated by the loop header + dominated_subgraph: nx.DiGraph = cfg.subgraph(n for n in cfg.nodes if dominates(node, n)) + reverse_reachability_map = nx.single_source_shortest_path_length(dominated_subgraph.reverse(), source=node) + # a node is in the loop if there is a backwards path to the header that doesn't leave the loop + loop_nodes = [loop_node for loop_node, distance in reverse_reachability_map.items() if distance >= 0] + # extend loop nodes with their natural edges; you can't leave the loop without a jump of some kind + # also extend loop nodes with exception edges that do not leave the loop + natural_edges = [(u, v) for u, v, data in dominated_subgraph.edges(data=True) if data["type"] == ControlFlowEdgeType.NATURAL.value] + # also extend loop nodes with their conditional edges, excluding the loop header + conditional_edges = [(u, v) for u, v, data in dominated_subgraph.edges(data=True) if data["type"] in [ControlFlowEdgeType.TRUE_JUMP.value, ControlFlowEdgeType.FALSE_JUMP.value] and u != node] + internal_exception_edges = [(u, v) for u, v, data in dominated_subgraph.edges(data=True) if data["type"] == ControlFlowEdgeType.EXCEPTION.value and v is not outer_exception_handler] + natural_dominated_subgraph = dominated_subgraph.edge_subgraph(natural_edges + internal_exception_edges + conditional_edges) + loop_nodes = set(loop_nodes + [v for _, v in nx.edge_dfs(natural_dominated_subgraph, source=loop_nodes)]) + + # canonical loop exit can be misidentified in while trues that start with if statements + if canonical_loop_exit and any(exit_successor in loop_nodes for exit_successor in cfg.successors(canonical_loop_exit)): + canonical_loop_exit = None + + # There are 4 kinds of exits: + # 1. canonical exit (the conditional branch from the loop header) + # 2. break statement + # 3. return statement + # 4. raised exception caught outside loop + loop_exit_edges = [(src, dst) for src, dst in cfg.edges if src in loop_nodes and dst not in loop_nodes and cfg.get_edge_data(src, dst)["type"] != ControlFlowEdgeType.META.value] + + loop_successor = None + break_edges = [] + for loop_node, exit_node in loop_exit_edges: + # skip the canonical exit + if loop_node is node and exit_node is canonical_loop_exit: + continue + + # skip exception edges to the outer handler + if cfg.get_edge_data(loop_node, exit_node)["type"] == ControlFlowEdgeType.EXCEPTION.value and exit_node is outer_exception_handler: + continue + + # all other cases are exhausted, so we are now only considering break statements + if loop_successor is None: + loop_successor = exit_node + elif loop_successor != exit_node: + if os.environ.get("DEBUG_CFLOW", None) == "1": + breakpoint() + raise RuntimeError("Found multiple break targets in the same loop!") + + break_edges.append((loop_node, exit_node)) + + # if there are no break statements, then the successor is the canonical exit + # the canonical exit may be different in the case of a loop-else, but that only matters if there are breaks + if loop_successor is None: + loop_successor = canonical_loop_exit + + # continue edges are all the latching nodes; may be explicit or implicit + continue_edges = [(src, node) for src in latching_nodes] + + # if we found nothing to refine, then exit + if not continue_edges and not break_edges: + return None + + # reduce the break/continue edges + reduced_cfg = cfg.copy() + for continue_edge in set(continue_edges): + LoopExitTemplate.structure_edge_inplace(reduced_cfg, continue_edge, exit_statment="continue") + + for break_edge in set(break_edges): + LoopExitTemplate.structure_edge_inplace(reduced_cfg, break_edge, exit_statment="break") + + # partially structure the loop while we have the information available + # if the canonical exit is not the successor, then the canonical exit is a loop else + if canonical_loop_exit is not None and loop_successor is not None and canonical_loop_exit != loop_successor: + loop_else_out_edges = get_out_edge_dict(reduced_cfg, canonical_loop_exit) + if loop_else_out_edges["natural"] is not None and loop_else_out_edges["natural"][0] != loop_successor: + # todo: fix triple nested loop w else break + e = (canonical_loop_exit, loop_else_out_edges["natural"][0]) + if dominates(e[1], e[0]): + # backwards edge + canonical_loop_exit = LoopExitTemplate.structure_edge_inplace(reduced_cfg, e, exit_statment="continue") + else: + canonical_loop_exit = LoopExitTemplate.structure_edge_inplace(reduced_cfg, e, exit_statment="break") + PreRefinedLoopTemplate.structure_nodes_inplace(reduced_cfg, loop_header=node, canonical_loop_exit=canonical_loop_exit, loop_successor=loop_successor) + + return reduced_cfg + + +def get_line_out_edge_dict(cfg: nx.DiGraph, insts: list[Inst]) -> dict[str, tuple[Any, ControlFlowEdgeType]]: + # check that all outgoing edges of a given category have the same target + line_out_edge_dict = dict() + for inst in insts: + for edge_category, (edge_target, edge_data) in get_out_edge_dict(cfg, inst).items(): + # skip considering internal control flow + if edge_target is None or edge_target in insts: + continue + # add edge to line-level mapping if this is the first time we've seen it + if edge_category not in line_out_edge_dict: + line_out_edge_dict[edge_category] = (edge_target, edge_data["type"]) + # reject inconsistent mappings; this line cannot be condensed + elif edge_target != line_out_edge_dict[edge_category]: + return None + return line_out_edge_dict + + +def condense_lines(cfg: nx.DiGraph, bytecode: EditableBytecode) -> nx.DiGraph: + lno_insts = bytecode.get_lno_insts() + for line_number, insts in lno_insts.items(): + insts = [inst for inst in insts if inst in cfg.nodes] # discard unreachable instructions + if not insts: + continue + line_in_edges = cfg.in_edges(nbunch=insts, data=True) + # check that no edges come from the outside to the middle of the line (sanity check) + incoming_edges = [(src, dst, data) for src, dst, data in line_in_edges if src not in insts] + if any(dst != insts[0] for src, dst, data in incoming_edges): + continue + + line_out_edge_dict = get_line_out_edge_dict(cfg, insts) + if line_out_edge_dict is None: + continue + + # group up all the instructions in the line into a LineTemplate + line_template = LineTemplate(*[InstructionTemplate(inst) for inst in insts]) + cfg.remove_nodes_from(insts) + cfg.add_node(line_template) + cfg.add_edges_from((src, line_template, data) for src, dst, data in incoming_edges) + for edge_category, (target, edge_type) in line_out_edge_dict.items(): + cfg.add_edge(line_template, target, type=edge_type) + + +def condense_basic_blocks(cfg: nx.DiGraph) -> nx.DiGraph: + structured_cfg = cfg.copy() + for node in list(structured_cfg.nodes): + if node == "START": + continue + candidate_cfg = LinearSequenceTemplate.try_to_match_node(structured_cfg, node) + if candidate_cfg is not None: + structured_cfg = candidate_cfg + return structured_cfg + + +def structure_control_flow(cfg: nx.DiGraph, bytecode: EditableBytecode) -> ControlFlowTemplate: + # group lines with no weird control flow into LineTemplates + # currently reduces overall performance on 3.9 + # condense_lines(cfg, bytecode) + + # 1. wrap instructions globally + structured_cfg = InstructionTemplate.match_graph(cfg) + root_node = min([inst_template for inst_template in structured_cfg.nodes], key=lambda inst_template: inst_template.get_instructions()[0].offset) + structured_cfg.add_nodes_from(["START", "END"]) + structured_cfg.add_edge("START", root_node, type="meta") + structured_cfg.add_edges_from((inst_template, "END", {"type": "meta"}) for inst_template in structured_cfg.nodes if isinstance(inst_template, InstructionTemplate) and inst_template.instruction.opname in ["RETURN_VALUE", "RETURN_CONST"]) + + modification_counter = 0 + # 2. match linear sequences globally + structured_cfg = condense_basic_blocks(structured_cfg) + + # 3. repeat until the graph has no non-meta edges + # 3a. Check for matches on loop templates + # 3b. Check for matches on non-loop templates + # 3c. Check for matches on exception templates + visualize(structured_cfg, bytecode.name, modification_counter) + + def fully_structured(cfg: nx.DiGraph) -> bool: + # if there are any non-meta edges, the control flow is not fully structured + if any(edge_type != ControlFlowEdgeType.META.value for _, _, edge_type in structured_cfg.edges(data="type")): + return False + # if there is more than one node other than START and END, the control flow is not fully structured + if len(cfg) > 3: + return False + return True + + infinite_loop_detection_threshold = 50 + + while not fully_structured(structured_cfg): + modified = False + for acyclic_templates in get_acyclic_template_passes(version=bytecode.version.as_tuple()): + current_num_nodes = len(structured_cfg.nodes) + for node in nx.dfs_postorder_nodes(structured_cfg, source="START"): + # don't process the start node + if node in ["START", "END"]: + continue + + if new_cfg := structure_loop(structured_cfg, node): + structured_cfg = new_cfg + modified = True + modification_counter += 1 + visualize(structured_cfg, bytecode.name, modification_counter) + break + + # check acyclic patterns if no cyclic pattern was matched + for template in acyclic_templates: + candidate_cfg = template.try_to_match_node(structured_cfg, node) + if candidate_cfg is not None: + structured_cfg = candidate_cfg + modified = True + modification_counter += 1 + visualize(structured_cfg, bytecode.name, modification_counter) + break + + if modified: + break + + if modified: + break + + if not modified: + # if in debug mode and template is irreducible breakpoint to inspect cfg + if os.environ.get("DEBUG_CFLOW", None) == "1": + breakpoint() + return IrreduciblePlaceholderTemplate("irreducible") + else: + new_num_nodes = len(structured_cfg) + if new_num_nodes >= current_num_nodes: + infinite_loop_detection_threshold -= 1 + else: + infinite_loop_detection_threshold = 50 + + if infinite_loop_detection_threshold <= 0: + return IrreduciblePlaceholderTemplate("infinite grammar loop") + + structured_cfg.remove_nodes_from(["START", "END"]) + + return list(structured_cfg.nodes)[0] diff --git a/pylingual/decompiler.py b/pylingual/decompiler.py new file mode 100644 index 0000000..24d0783 --- /dev/null +++ b/pylingual/decompiler.py @@ -0,0 +1,499 @@ +from __future__ import annotations + +import datetime +import functools +import importlib.resources +import itertools +import keyword +import logging +import re +import tempfile +import shutil +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING + +from xdis.magics import magicint2version + +from pylingual.control_flow_reconstruction.cflow import bytecode_to_indented_source +from pylingual.control_flow_reconstruction.reconstruct_control_indentation import reconstruct_source +from pylingual.equivalence_check import TestResult, compare_pyc +from pylingual.models import CacheTranslator, load_models +from pylingual.utils.generate_bytecode import CompileError, compile_version +from pylingual.masking.model_disasm import create_global_masker, restore_masked_source_text +from pylingual.editable_bytecode import PYCFile +from pylingual.segmentation.segmentation_search_strategies import get_top_k_predictions, m_deep_top_k, naive_confidence_priority, filter_subwords +from pylingual.segmentation.sliding_window import merge, sliding_window +from pylingual.utils.lists import unflatten +from pylingual.utils.version import PythonVersion +from pylingual.utils.tracked_list import CFLOW_STEP, CORRECTION_STEP, SEGMENTATION_STEP, TrackedList, TrackedDataset + +if TYPE_CHECKING: + import transformers + from pylingual.editable_bytecode.Instruction import Inst + +logger = logging.getLogger(__name__) + +bytecode_separator = " " +lno_regex = re.compile(r"(?<=line )\d+") +def_regex = re.compile(r"(?<=def ).+?(?=\()") +class_regex = re.compile(r"(?<=class ).+?(?=:|\()") + + +def has_comp_error(results: list[TestResult]) -> bool: + return bool(results) and isinstance(results[0], Exception) + + +@dataclass +class DecompilerResult: + """ + Dataclass containing relevant results from decompiling a pyc + + :param equivalence_results: list of internal bytecode comparison results + :param original_pyc: path to original pyc + :param decompiled_source: path to decompiled source + :param out_dir: directory where decompiler output and internal steps are written + :param version: python version of pyc + """ + + equivalence_results: list[TestResult] + original_pyc: Path + decompiled_source: Path + out_dir: Path + version: PythonVersion + + def calculate_success_rate(self) -> float: + if not self.equivalence_results: + return 0 + return sum(1 for x in self.equivalence_results if x.success) / len(self.equivalence_results) * 100 + + +class Decompiler: + """ + You probably want to use decompile() instead. + + Decompiles a PYC file after masking bytecode, segmenting bytecode, and translating bytecode back into source statements, then reconstructs the control flow. + Additionally saves the decompiled file into the specified output directory. + + :param pyc: The PYCFile loaded into memory + :param out_dir: The output directory where decompilation results will be stored + :param segmenter: The loaded segmentation model + :param translator: The loaded translation model + :param version: The python version + :param top_k: Value of k to use for top k segmentation + :param trust_lnotab: Decides whether or not to use line number information + """ + + def __init__(self, pyc: PYCFile, out_dir: Path, segmenter: transformers.Pipeline, translator: CacheTranslator, version: PythonVersion, top_k=10, trust_lnotab=False): + self.pyc = pyc + self.file = pyc.pyc_path + self.out_dir = out_dir + self.segmenter = segmenter + self.translator = translator + self.version = version + self.out_dir.mkdir(parents=True, exist_ok=True) + + self.top_k = top_k + self.highest_k_used = 0 + + self.trust_lnotab = trust_lnotab + + self.header = "# Decompiled with PyLingual (https://pylingual.io)\n" + try: + self.header += ( + f"# Internal filename: {self.pyc.codeobj.co_filename}\n" + f"# Bytecode version: {magicint2version[self.pyc.magic]} ({self.pyc.magic})\n" + f"# Source timestamp: {datetime.datetime.fromtimestamp(self.pyc.timestamp, datetime.UTC).strftime('%Y-%m-%d %H:%M:%S UTC')} ({self.pyc.timestamp})\n\n" + ) + except: + pass + + self.decompile() + self.log_results() + + logger.info(f"Checking decompilation for {self.file.name}...") + if shutil.which("pyenv") is None and self.version != sys.version_info: + logger.warning(f"pyenv is not installed so equivalence check cannot be performed. Please install pyenv manually along with the required Python version ({self.version}) or run PyLingual again with the --init-pyenv flag") + self.result = DecompilerResult([TestResult(False, "Cannot compare equivalence without pyenv installed", bc.name, bc.name) for bc in self.pyc.iter_bytecodes()], self.file, self.candidate_source_path, self.out_dir, self.version) + return + + self.equivalence_results = self.check_reconstruction() + self.correct_failures() + + if has_comp_error(self.equivalence_results): + self.equivalence_results += self.purge_comp_errors() + + equivalence_report = self.out_dir / "equivalence_report.txt" + equivalence_report.write_text("\n".join(str(r) for r in self.equivalence_results)) + + self.result = DecompilerResult(self.equivalence_results, self.file, self.candidate_source_path, self.out_dir, self.version) + + def decompile(self): + self.mask_bytecode() + if self.trust_lnotab: + self.update_segmentation_from_lnotab() + else: + self.run_segmentation() + self.run_translation() + self.run_cflow_reconstruction() + self.reconstruct_source() + + def find_comp_error_cause(self, results: list[TestResult]): + # parse lno from exception + lno = int(lno_regex.search(str(results[0])).group(0)) - 1 + # adjust for lines added in postprocessing + lno -= sum(1 for x in (self.header + self.indented_source).split("\n")[: lno + 1] if x.endswith("# postinserted") or not x.strip() or x.strip().startswith("#")) + + # get offending codeobj + + bad_codeobj = self.blame[lno] + + bad_idx = next(i for i, e in enumerate(self.ordered_bytecodes) if e.codeobj == bad_codeobj) + return bad_idx + + def correct_failures(self): + changed = False + + try: + # fix compile errors + corrected_comp_errors = set() + while has_comp_error(self.equivalence_results): + bad_idx = self.find_comp_error_cause(self.equivalence_results) + # i don't think this will ever happen but better safe than sorry + if bad_idx in corrected_comp_errors: + return + if not self.correct_segmentation(bad_idx, from_comp_error=True): + return + changed = True + corrected_comp_errors.add(bad_idx) + failed = TrackedList(CORRECTION_STEP, [i for i, result in enumerate(self.equivalence_results) if not result.success]) + for i in failed: + if self.correct_segmentation(i): + changed = True + continue + # other fixes... + except Exception as e: + e.add_note("From error correction") + raise + finally: + if changed: + self.log_results() + + # get eq results after replacing all codeobjs with comp errors with pass, preserving nested codeobjs + def purge_comp_errors(self): + try: + equivalence_results = self.equivalence_results + + def replace_line(line): + line = line.strip() + x = def_regex.search(line) + if x is not None: + try: + x = x.group(0) + x = self.global_masker.unmask(x) if x.startswith("= 2: + bounds = "B" + "I" * (len(bc_line) - 2) + "E" + else: + raise ValueError("Unexpected amount of bytecodes segmented into a line") + boundaries.extend(list(bounds)) + + self.segmentation_results.append([{"entity": entity, "score": 1} for entity in boundaries]) + + self.update_starts_line() + + def run_translation(self): + logger.info(f"Translating statements for {self.file.name}...") + try: + translation_requests = [] + for instructions, boundary_predictions in zip(self.ordered_instructions, self.segmentation_results): + translation_requests.append(self.make_translation_request(instructions, boundary_predictions)) + flattened_translation_requests = list(itertools.chain.from_iterable(translation_requests)) + self.translation_results = self.translator(flattened_translation_requests) + unflatten(self.translation_results, translation_requests) + self.update_source_lines() + except Exception as e: + e.add_note("From translation") + raise + + def run_cflow_reconstruction(self): + logger.info(f"Reconstructing control flow for {self.file.name}...") + try: + self.cflow_results = {bc.codeobj: bytecode_to_indented_source(bc, self.source_lines) for bc in TrackedDataset(CFLOW_STEP, self.ordered_bytecodes)} + except Exception as e: + e.add_note("From control flow reconstruction") + raise + + # merge sources and unmask source + def reconstruct_source(self): + logger.info(f"Reconstructing source for {self.file.name}...") + # merge sources and postprocess results + try: + self.indented_masked_source, self.blame = reconstruct_source(self.pyc, {a: b for a, b in self.cflow_results.items()}) + except Exception as e: + e.add_note("From control flow reconstruction") + raise + # undo the masking + try: + self.indented_source = restore_masked_source_text(self.indented_masked_source, self.global_masker, python_version=self.version) + except Exception as e: + e.add_note("From unmasking source") + raise + + # write indented source and pyc + def log_results(self): + self.candidate_source_path = self.out_dir / self.file.with_suffix(".py").name + self.candidate_pyc_path = self.candidate_source_path.with_suffix(".pyc") + self.candidate_source_path.write_text(self.header + self.indented_source) + try: + compile_version(self.candidate_source_path, self.candidate_pyc_path, self.version) + except Exception: + pass # it's ok if the python doesn't compile + + # make a translation request from a segmentation result + def make_translation_request(self, instructions: list[Inst], boundary_predictions: list[dict]) -> list[str]: + translation_requests = [] + for inst, boundary_prediction in zip(instructions, boundary_predictions): + if boundary_prediction["entity"] == "B": + translation_requests.append(self.global_masker.get_model_view(inst)) + else: + translation_requests[-1] += bytecode_separator + self.global_masker.get_model_view(inst) + return translation_requests + + def update_source_lines(self): + self.source_lines = list(itertools.chain.from_iterable(self.translation_results)) + if self.version == (3, 12): + self.pyc.fix_while12(self.source_lines) + elif self.version >= (3, 10): + self.pyc.fix_while(self.source_lines) + + # compiles and compares result to original pyc + def check_reconstruction(self, write_source=False) -> list: + candidate_source_path = self.candidate_source_path + candidate_pyc_path = self.candidate_pyc_path + if write_source: + tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".py") + tmp.write(self.header + self.indented_source) + candidate_source_path = Path(tmp.name) + candidate_pyc_path = Path(tmp.name).with_suffix(".pyc") + + # compile source + try: + compile_version(candidate_source_path, candidate_pyc_path, self.version) + except CompileError as e: + return [e] + else: + return compare_pyc(self.file, candidate_pyc_path) + + # try to correct the segmentation of the ith code object + def correct_segmentation(self, i: int, from_comp_error=False) -> bool: + if not self.segmentation_results[i]: + return False + original_prediction = [r["entity"] for r in self.segmentation_results[i]] + strategy = functools.partial(m_deep_top_k, priority_function=naive_confidence_priority, m=2, k=self.top_k + 1) + # skip first prediction since it is the same as original + for k, prediction in enumerate(get_top_k_predictions(strategy, self.segmentation_results[i])[1:], start=1): + if prediction[0] != "B": + continue + # change segmentation to new prediction + for r, p in zip(self.segmentation_results[i], prediction): + r["entity"] = p + self.update_starts_line() + # retranslate affected bytecode + translation_request = self.make_translation_request(self.ordered_instructions[i], self.segmentation_results[i]) + try: + self.translation_results[i] = self.translator(translation_request) + self.update_source_lines() + except Exception as e: + e.add_note("From translation") + raise + # redo cflow of affected bytecode + try: + bc = self.ordered_bytecodes[i] + self.cflow_results[bc.codeobj] = bytecode_to_indented_source(bc, self.source_lines) + except Exception as e: + e.add_note("From control flow reconstruction") + raise + # check if new reconstruction is correct + previous_indented_masked_source, previous_blame, previous_indented_source = self.indented_masked_source, self.blame, self.indented_source + self.reconstruct_source() + equivalence_results = self.check_reconstruction(write_source=True) + if from_comp_error: + if not has_comp_error(equivalence_results) or self.find_comp_error_cause(equivalence_results) != i: + self.equivalence_results = equivalence_results + self.highest_k_used = max(self.highest_k_used, k) + return True + elif not has_comp_error(equivalence_results) and equivalence_results[i].success: + self.equivalence_results[i] = equivalence_results[i] + self.highest_k_used = max(self.highest_k_used, k) + return True + # correction failed, roll back changes to internal source code storage + self.indented_masked_source, self.blame, self.indented_source = previous_indented_masked_source, previous_blame, previous_indented_source + # revert to original segmentation + for r, p in zip(self.segmentation_results[i], original_prediction): + r["entity"] = p + return False + + # update starts_line of all instructions based on segmentation results + def update_starts_line(self): + line = 0 + for instructions, boundary_predictions in zip(self.ordered_instructions, self.segmentation_results): + for inst, boundary_prediction in zip(instructions, boundary_predictions): + if boundary_prediction["entity"] == "B": + line += 1 + inst.starts_line = line + else: + inst.starts_line = None + + +def decompile(file: Path, out_dir: Path, config_file: Path | None = None, version: PythonVersion | tuple[int, int] | str | None = None, top_k: int = 10, trust_lnotab: bool = False) -> DecompilerResult: + """ + Decompile a PYC file. + + :param file: path to pyc to decompile + :param out_dir: Path to save decompilation results and steps to. Defaults to ./decompiled_/ + :param config_file: Path to decompiler_config.yaml to load. recommended None, which loads the default pylingual config. + :param version: Loads the models corresponding to this python version. if None, automatically detects version based on input PYC file. + :param top_k: Max number of pyc segmentations to consider. + :param trust_lnotab: Trust the lnotab in the input PYC for segmentation, recommended False. + :return: DecompilerResult class including important information about decompilation + """ + logger.info(f"Loading {file}...") + pyc = PYCFile(file) + + # try to auto resolve version + if version is None: + try: + pversion = PythonVersion(pyc.version) + logger.info(f"Detected version as {pversion}") + except ValueError: + raise + except Exception as err: + raise TypeError("Error automatically parsing version from pyc") from err + else: + pversion = PythonVersion(version) + + # try auto load config file from package + if config_file is None: + pkg_path = importlib.resources.files("pylingual") + with importlib.resources.as_file(pkg_path.joinpath("decompiler_config.yaml")) as pylingual_config: + config_file = Path(pylingual_config) + + # check config exists + if not config_file.exists(): + raise FileNotFoundError(f"Decompiler config {config_file} not found") + + segmenter, translator = load_models(config_file, pversion) + + logger.info(f"Decompiling pyc {file.resolve()} to {out_dir.resolve()}") + result = Decompiler(pyc, out_dir, segmenter, translator, pversion, top_k, trust_lnotab).result + + logger.info("Decompilation complete") + logger.info(f"{round(result.calculate_success_rate(), 2)}% code object success rate") + logger.info(f"Result saved to {result.decompiled_source.resolve()}") + return result diff --git a/pylingual/decompiler_config.yaml b/pylingual/decompiler_config.yaml new file mode 100644 index 0000000..6b34438 --- /dev/null +++ b/pylingual/decompiler_config.yaml @@ -0,0 +1,87 @@ +v3.6: + SEGMENTATION_MODEL: + REPO: syssec-utd/py36-pylingual-v1-segmenter + REVISION: main + TOKENIZER: syssec-utd/py36-pylingual-v1-tokenizer + + STATEMENT_MODEL: + REPO: syssec-utd/py36-pylingual-v1-statement + REVISION: main + TOKENIZER: syssec-utd/py36-pylingual-v1-tok + +v3.7: + SEGMENTATION_MODEL: + REPO: syssec-utd/py37-pylingual-v1-segmenter + REVISION: main + TOKENIZER: syssec-utd/py37-pylingual-v1-tokenizer + + STATEMENT_MODEL: + REPO: syssec-utd/py37-pylingual-v1-statement + REVISION: main + TOKENIZER: syssec-utd/py37-pylingual-v1-tok + +v3.8: + SEGMENTATION_MODEL: + REPO: syssec-utd/py38-pylingual-v1-segmenter + REVISION: main + TOKENIZER: syssec-utd/py38-pylingual-v1-tokenizer + + STATEMENT_MODEL: + REPO: syssec-utd/py38-pylingual-v1-statement + REVISION: main + TOKENIZER: syssec-utd/py38-pylingual-v1-tok + +v3.9: + SEGMENTATION_MODEL: + REPO: syssec-utd/py39-pylingual-v1-segmenter + REVISION: main + TOKENIZER: syssec-utd/py39-pylingual-v1-tokenizer + + STATEMENT_MODEL: + REPO: syssec-utd/py39-pylingual-v1-statement + REVISION: main + TOKENIZER: syssec-utd/py39-pylingual-v1-tok + +v3.10: + SEGMENTATION_MODEL: + REPO: syssec-utd/py310-pylingual-v1-segmenter + REVISION: main + TOKENIZER: syssec-utd/py310-pylingual-v1-tokenizer + + STATEMENT_MODEL: + REPO: syssec-utd/py310-pylingual-v1-statement + REVISION: main + TOKENIZER: syssec-utd/py310-pylingual-v1-tok + +v3.11: + SEGMENTATION_MODEL: + REPO: syssec-utd/py311-pylingual-v1-segmenter + REVISION: main + TOKENIZER: syssec-utd/py311-pylingual-v1-tokenizer + + STATEMENT_MODEL: + REPO: syssec-utd/py311-pylingual-v1-statement + REVISION: main + TOKENIZER: syssec-utd/py311-pylingual-v1-tok + +v3.12: + SEGMENTATION_MODEL: + REPO: syssec-utd/py312-pylingual-v1-segmenter + REVISION: main + TOKENIZER: syssec-utd/py312-pylingual-v1-tokenizer + + STATEMENT_MODEL: + REPO: syssec-utd/py312-pylingual-v1-statement + REVISION: main + TOKENIZER: syssec-utd/py312-pylingual-v1-tok + +v3.13: + SEGMENTATION_MODEL: + REPO: syssec-utd/py313-pylingual-v1-segmenter + REVISION: main + TOKENIZER: syssec-utd/py313-pylingual-v1-tokenizer + + STATEMENT_MODEL: + REPO: syssec-utd/py313-pylingual-v1-statement + REVISION: main + TOKENIZER: syssec-utd/py313-pylingual-v1-tok diff --git a/pylingual/editable_bytecode/EditableBytecode.py b/pylingual/editable_bytecode/EditableBytecode.py new file mode 100644 index 0000000..2075ef8 --- /dev/null +++ b/pylingual/editable_bytecode/EditableBytecode.py @@ -0,0 +1,860 @@ +import copy as copy_module +import logging +import pprint +from collections import defaultdict +from typing import Any, Optional + +from xdis import Bytecode, iscode +from xdis.cross_dis import instruction_size, op_has_argument +from xdis.bytecode import parse_exception_table, _ExceptionTableEntry + +from pylingual.utils.version import PythonVersion + +from .Instruction import Inst +from .utils import codeobj_replace, comprehension_names, unwrap +from .control_flow_graph import bytecode_to_control_flow_graph + +from typing import Callable + + +class EditableBytecode: + """ + An editable representation of Pythonic bytecode. Many values in it, including its instructions, may be incorrect + until the final "bake," when it's converted back into raw bytecode. However, it supports arbitrary accessing, + insertion, deletion, and modification of instructions. It also annotates instructions and provides extra information + regarding the control flow they describe. + + Baking functions: to_code() (EditableBytecode -> code object) + to_bytecode() (EditableBytecode -> co_code [bytes]) + """ + + def __init__( + self, + codeobj, + opcode, + version, + name_prefix: Optional[str] = None, + parent=None, + ): + self.codeobj = codeobj + self.opcode = opcode + self.version = PythonVersion(version) + self.parent = parent + + self.co_consts = unwrap(list(codeobj.co_consts)) + self.co_names = unwrap(list(codeobj.co_names)) + self.co_varnames = unwrap(list(codeobj.co_varnames)) + + self.name_prefix = unwrap(name_prefix) + self.name = unwrap(codeobj.co_name) if name_prefix is None else unwrap(name_prefix) + "." + unwrap(codeobj.co_name) + + self._edited = False + + self.instructions = [Inst.from_instruction(self, inst) for inst in Bytecode(self.codeobj, self.opcode)] + + self.offsets = {inst.offset: inst for inst in self.instructions} + jump_targets = [inst.argval for inst in self.instructions if inst.is_jump] + for inst in self.instructions: + inst.is_jump_target = inst.offset in jump_targets + + # named exception table is a named tuple of the exception entries. + # should be defined when an exception_table exists for a codeobj in python versions >= (3,11) + self.named_exception_table = None + if hasattr(codeobj, "co_exceptiontable"): + self.named_exception_table = parse_exception_table(codeobj.co_exceptiontable) + + # maps starting offsets to ending offsets and target offsets + self.exception_table = {} + # maps ending offsets to the cause of the entry (i.e. with, finally, or except) + self.end_table = {} + + # check for globals and nonlocals + self.globals = set() + self.nonlocals = set() + for inst in self.instructions: + if inst.opname == "STORE_GLOBAL": + self.globals.add(inst.argval) + elif inst.opname == "STORE_DEREF": + self.nonlocals.add(inst.argval) + + if self.version >= (3, 11): + self.fix_format_string_lno() + + # Prepare "deep analysis" structures - cycle detection and annotation + self._preprocess_jumps() + + low_information_instruction_blacklist = ["RESUME", "EXTENDED_ARG", "CACHE", "PRECALL", "MAKE_CELL"] + self.remove_instructions({inst for inst in self.instructions if inst.opname in low_information_instruction_blacklist}) + + # updates attribute of instructions that contains information about the exception table + self._add_inst_exception_attrs() + + # Represents subsidiary EditableBytecode objects + self.child_bytecodes = [] + self.bytecode_lookup = {} + + # Initialize recursive structure + for i, const in enumerate(self.co_consts): + if iscode(const): + self.co_consts[i] = EditableBytecode(const, opcode, self.version, name_prefix=self.name, parent=self) + self.child_bytecodes.append(self.co_consts[i]) # Keeps it in order + self.bytecode_lookup[const.co_name] = self.co_consts[i] + + def get_recursive_length(self): + """Returns the recursive length of this bytecode and all its descendents""" + return len(self) + sum(bytecode.get_recursive_length() for bytecode in self.child_bytecodes) + + # + # INTERNAL PROCESSING + # + def _preprocess_jumps(self): + """Preprocesses jumps by setting the instruction's "target" attribute to the Inst object rather than the numeric + offset. O(N^2).""" + for inst in self.instructions: + target = self.get_jump_target_offset(inst) + + if target is not None: + try: + inst._target = self.get_by_offset(target) + except KeyError: + raise ValueError(f"Found invalid target when preprocessing jumps: offset {target} for instruction {inst}") + + def regenerate(self): + """Regenerates correct offsets for the instruction list and resets the _edited field. This is called + automatically, but calling it externally should do no harm. O(N).""" + if not self._edited: + return + + self.offsets = {} + offset = 0 + for inst in self.instructions: + inst.original_offset = offset + self.offsets[offset] = inst + + offset += inst.real_size + + self._edited = False + + def create_arg_inst(self, opname: str, arg, offset: int = -1): + opcode = getattr(self.opcode, opname) + return Inst(self, opname, opcode, None, instruction_size(opcode, self.opcode), arg, arg, repr(arg), True, offset, None, False, False) + + def _bake_jumps(self, add_extended=False): + """ + Bakes the correct jump target address into each jump's "arg" attribute and ensures the number of EXTENDED_ARGs preceeding it is + sufficient. This is automatically called by to_bytecode. O(N^2). + """ + + while True: + len_before = len(self) + changed = False + + i = 0 + while i < len(self): + instruction = self.instructions[i] + + if instruction.has_arg: + if instruction.is_jump: + # ensure that we jump to the start of the preceeding EXTENDED_ARG chain + current_jump_target_index = self.instructions.index(instruction.target) + for prev_instruction in self.instructions[current_jump_target_index - 1 :: -1]: + if prev_instruction.opcode == self.opcode.EXTENDED_ARG: + instruction.target = prev_instruction + changed = True + else: + # we have reached the end of the EXTENDED_ARG chain + break + + if instruction.is_rel_jump: + instruction.arg = instruction.target.offset - instruction.offset - instruction.real_size + if self.version >= (3, 10): + instruction.arg = int(instruction.arg / 2) + instruction.argval = instruction.target.offset + elif instruction.is_abs_jump: + if self.version >= (3, 10): + instruction.arg = int(instruction.target.offset / 2) + instruction.argval = instruction.target.offset + else: + instruction.arg = instruction.argval = instruction.target.offset + if add_extended: + # count the number of necessary preceeding EXTENDED_ARG instructions + n_extendeds_needed = (instruction.arg > 0xFF) + (instruction.arg > 0xFFFF) + (instruction.arg > 0xFFFFFF) + + # count the number of preceding EXTENDED_ARG instructions present in the bytecode + n_extendeds_found = 0 + for current_instruction in self.instructions[i - 1 :: -1]: + if current_instruction.opcode == self.opcode.EXTENDED_ARG: + n_extendeds_found += 1 + else: + break + + if n_extendeds_found > 3: + raise ValueError("Incorrect bytecode -- more than 3 extended args found before instruction " + repr(instruction)) + + if instruction.arg > 0xFFFFFFFF: + raise ValueError("Incorrect bytecode -- more than 3 extended args needed for instruction" + repr(instruction)) + + new_extendeds = [self.create_arg_inst("EXTENDED_ARG", 0) for _ in range(n_extendeds_needed - n_extendeds_found)] + + if new_extendeds: + self[i:i] = new_extendeds + i += len(new_extendeds) + + n_extendeds = max([n_extendeds_found, n_extendeds_needed]) + for j in range(n_extendeds): + self[i - j - 1].arg = ((0xFF << ((j + 1) * 8)) & instruction.arg) >> ((j + 1) * 8) + + instruction.has_extended_arg = True + + i += 1 + + if len(self) == len_before and not changed: + break + + self.regenerate() + + ## INSTRUCTION LOOKUP + + def get_offset(self, instruction): + """Gets the real, current offset of the instruction. O(N).""" + self.regenerate() + return instruction.original_offset # Prevent theoretically-impossible infinite loops + + def get_jump_target_offset(self, instruction): + """Returns the target initial offset of a jump instruction, or None if the instruction is not a jump. O(1).""" + if instruction.is_abs_jump: + # duct taping; arg is incorrect in 310 + return instruction.argval + + elif instruction.is_rel_jump: + # duct taping; arg is incorrect in 310 + return instruction.argval + + return None + + def get_by_offset(self, offset): + """Finds the instruction by its offset. O(N).""" + self.regenerate() + return self.offsets[offset] + + def _get_instruction_after(self, instruction): + """Gets the instruction directly following the specified instruction, or None if it's at the end of the bytecode. O(N).""" + next_offset = instruction.offset + instruction.real_size + + try: + return self.get_by_offset(next_offset) + except KeyError: + return None + + def resolve_namespace(self, value: Any) -> Any: + if not isinstance(value, str): + return value + + # true dunder methods (e.g., __init__, __myfunc__) don't get namespaced + if value.endswith("__"): + return value + + # try namespaces all the way up the tree + def namespace_generator(): + current = self + yield f"_{current.codeobj.co_name.lstrip('_')}" + while current.parent: + current = current.parent + yield f"_{current.codeobj.co_name.lstrip('_')}" + + for namespace in namespace_generator(): + if value.startswith(f"{namespace}__"): + return value[len(namespace) :] + + return value + + ## SUBSIDIARY OBJECTS + + def iter_bytecodes(self): + """Iterates through all EditableBytecode objects in the recursive object.""" + + yield self + + for bytecode in self.child_bytecodes: + for bc in bytecode.iter_bytecodes(): + yield bc + + def copy(self): + """Returns a copy of the current bytecode object, as it stands.""" + try: + copy = EditableBytecode( + self.to_code(), + self.opcode, + self.version, + self.name_prefix, + False, + ) + except IndexError: + # A sketchy workaround for what seems to be a strange bug with loading saved code objects + copy = copy_module.copy(self) + copy._edited = True + + instructions = [copy_module.copy(inst) for inst in copy.instructions] + for inst in instructions: + if inst.is_jump: + inst.target = instructions[copy.instructions.index(inst.target)] + copy.instructions = instructions + + copy.child_bytecodes = [] + for const, i in enumerate(copy.co_consts): + if isinstance(const, EditableBytecode): + new = const.copy() + new.parent = copy + + copy.co_consts[i] = new + copy.bytecode_lookup[const.codeobj.co_name] = new + copy.child_bytecodes.append(new) + + copy.regenerate() + + return copy + + ## BYTECODE PREPARATION + + def _gen_lines(self, line_start=None): + """Generates a line number table (co_lines) from the current instructions.""" + lines_table = [] + offset = 0 + last_line = line_start or getattr(self.codeobj, "co_firstlineno", None) + + for instruction in self: + if instruction.starts_line: + if offset: + if last_line is None: + lines_table.append(offset) + lines_table.append(-128) + else: + line_delta = instruction.starts_line - last_line + + while line_delta > 127: + lines_table.append(0) + lines_table.append(127) + line_delta -= 127 + + while line_delta < -127: + lines_table.append(0) + lines_table.append(-127) + line_delta += 127 + + lines_table.append(offset) + lines_table.append(line_delta) + + last_line = instruction.starts_line + + offset += instruction.real_size + + return bytes(lines_table) + + def to_bytecode(self): + """Converts the instruction list into valid bytecode, to be used with co_code. This was partially taken from the incomplete + list2bytecode function in xdis. O(N).""" + self._bake_jumps() + + bc = [] + for instruction in self: + bc.extend([instruction.opcode, (instruction.arg & 0xFF) if instruction.arg is not None else 0]) + + return bytes(bc) + + def to_code(self, no_lnotab=False): + """Returns a fully-functioning 'code' or xdis code object from this edited bytecode.""" + + # Here, we hotfix the potentially-edited EditableBytecode objects within this one's constant + # array. This used to be processed by recursive_fix, but it makes more sense to have the + # EditableBytecode objects do it directly. + co_consts = tuple((const.to_code(no_lnotab=no_lnotab) if isinstance(const, EditableBytecode) else const) for const in self.co_consts) + + replacement_args = { + "co_code": self.to_bytecode(), + "co_consts": tuple(co_consts), + "co_names": tuple(self.co_names), + "co_varnames": tuple(self.co_varnames), + } + + # co_lnotab deprecated in python >= 3.10 + if self.version < (3, 10): + replacement_args.update({"co_lnotab": b"\x00\x01" if no_lnotab else self._gen_lines()}) + + return codeobj_replace(self.codeobj, **replacement_args) + + ### INSTRUMENTATION FUNCTIONS + + def apply_patches(self, patch_functions: list[Callable[["EditableBytecode"], None]]) -> None: + for patch in patch_functions: + for bc in self.iter_bytecodes(): + patch(bc) + + def _change_jump_targets(self, from_inst: Inst, to_inst: Inst): + """Changes the targets of any instructions jumping to "from_inst" to "to_inst". + Before: + InstA --> InstB + After _change_jump_targets(InstB, InstC): + InstA --> !!InstC!! + """ + for i, inst in enumerate(self): + if inst.is_jump and inst.target == from_inst: + self[i]._target = to_inst + + def collapse_unconditional_jumps(self): + """Causes unnecessary unconditional jumps to "collapse" into a single jump.""" + + unconditional_jumps = set(instruction for instruction in self if instruction.is_uncond_jump and instruction.target.is_uncond_jump) + instructions_changed = set() + + while unconditional_jumps: + this_layer = set(jump for jump in unconditional_jumps if jump.target not in unconditional_jumps) + + if not this_layer: + logger = logging.getLogger("transform") + logger.warning("Two jumps are forming an infinite loop!\n" + pprint.pformat(unconditional_jumps)) + break + + for jump in this_layer: + jump._target = jump._target._target + instructions_changed.add(jump) + + unconditional_jumps.difference_update(this_layer) + + return len(instructions_changed) + + def remove_unreachable_instructions(self): + """Removes unreachable instructions from the bytecode as cleanly as possible.""" + + self.regenerate() + + cfg = bytecode_to_control_flow_graph(self) + unreachable_instructions = set(self.instructions) - set(cfg.nodes) + return self.remove_instructions(unreachable_instructions) + + def remove_useless_jumps(self): + """Removes jumps that just jump to the next instruction.""" + useless_jumps = {inst for inst in self if inst.is_jump and inst.target.offset == inst.offset + inst.real_size} + return self.remove_instructions(useless_jumps) + + def shrink(self): + """Shrinks and optimizes bytecode using various heuristics.""" + n = self.collapse_unconditional_jumps() + n += self.remove_unreachable_instructions() + n += self.remove_useless_jumps() + + return n + + def remove_instructions(self, to_remove): + """Removes every instruction in the provided list *fairly* gracefully, albeit not perfectly when ambiguity is involved.""" + if len(to_remove) == 0: + return 0 + + self.regenerate() + self._edited = True + + # store a list of all jumps to avoid repeatedly searching for them + jumps = [inst for inst in self.instructions if inst.is_jump] + + # store an instruction-based copy of the exception table to make offset fixing easier at the end + temp_exception_table = {self.get_by_offset(start): (self.get_by_offset(end), self.get_by_offset(target)) for start, (end, target) in self.exception_table.items()} + temp_named_exception_table = dict() + if self.named_exception_table: + temp_named_exception_table = [(self.get_by_offset(e.start), self.get_by_offset(e.end), self.get_by_offset(e.target), e.depth, e.lasti) for e in self.named_exception_table] + + # propagate jump targets backwards from the end of the bytecode and remove immediately + removed = set() + while self.instructions[-1] in to_remove: + # update jump targets + new_jump_target = self.instructions[-2] + for jump in jumps: + if jump.target is self.instructions[-1]: + jump._target = new_jump_target + new_jump_target.is_jump_target = True + + # update exception table entries + new_exception_target = self.instructions[-2] + for start, (end, target) in list(temp_exception_table.items()): + # move start back if deleted + if start is self.instructions[-1]: + temp_exception_table[new_exception_target] = (end, target) + del temp_exception_table[start] + start = new_exception_target + # move end back if deleted + if end is self.instructions[-1]: + temp_exception_table[start] = (new_exception_target, target) + end = new_exception_target + # move target back if deleted + if target is self.instructions[-1]: + temp_exception_table[start] = (end, new_exception_target) + + # update named exception table entries + for exception_index, (start, end, target, depth, lasti) in enumerate(list(temp_named_exception_table)): + if start is self.instructions[-1]: + temp_named_exception_table[exception_index] = (new_exception_target, end, target, depth, lasti) + start = new_exception_target + if end is self.instructions[-1]: + temp_named_exception_table[exception_index] = (start, new_exception_target, target, depth, lasti) + end = new_exception_target + if target is self.instructions[-1]: + temp_named_exception_table[exception_index] = (start, end, new_exception_target, depth, lasti) + + removed.add(self.instructions.pop()) + + for inst in sorted(to_remove, key=lambda x: x.offset): + if inst in removed: + continue + + next_instruction = self._get_instruction_after(inst) + + # update line starts + if next_instruction.starts_line is None: + next_instruction.starts_line = inst.starts_line + + # update jump targets + for jump in jumps: + if jump.target is inst: + jump._target = next_instruction + next_instruction.is_jump_target = True + + # update exception table entries + new_exception_target = next_instruction + for start, (end, target) in list(temp_exception_table.items()): + # move start back if deleted + if start is inst: + temp_exception_table[new_exception_target] = (end, target) + del temp_exception_table[start] + start = new_exception_target + # move end back if deleted + if end is inst: + temp_exception_table[start] = (new_exception_target, target) + end = new_exception_target + # move target back if deleted + if target is inst: + temp_exception_table[start] = (end, new_exception_target) + + # update named exception table entries + for exception_index, (start, end, target, depth, lasti) in enumerate(list(temp_named_exception_table)): + if start is inst: + temp_named_exception_table[exception_index] = (new_exception_target, end, target, depth, lasti) + start = new_exception_target + if end is inst: + temp_named_exception_table[exception_index] = (start, new_exception_target, target, depth, lasti) + end = new_exception_target + if target is inst: + temp_named_exception_table[exception_index] = (start, end, new_exception_target, depth, lasti) + + # Now, removing them this way will have no side effects. + self.instructions = [inst for inst in self.instructions if inst not in to_remove] + self._edited = True + self.regenerate() # recalculate offsets + + # fix jump target argval and argrepr + for jump in jumps: + jump.argval = jump.target.offset + jump.argrepr = f"to {jump.argval}" + + # fix exception table offsets + self.exception_table = {start.offset: (end.offset, target.offset) for start, (end, target) in temp_exception_table.items()} + if temp_named_exception_table: + self.named_exception_table = [_ExceptionTableEntry(start.offset, end.offset, target.offset, depth, lasti) for (start, end, target, depth, lasti) in temp_named_exception_table] + self._add_inst_exception_attrs() + + self._edited = True + + return len(to_remove) + + def new_instruction(self, *args, **kwargs): + """Creates a new instruction for use with this EditableBytecode object. This function does NOT automatically insert the instruction.""" + return Inst(self, *args, **kwargs) + + ## OTHER UTILS + + def disasm_view(self, b_iter_bytecodes: bool = True) -> str: + """Get a multi-line disassembled view of this bytecode obj + b_iter_bytecodes (bool) True : Iter through all bytecodes in self (child bytecodes)""" + disview = "" + for bytecode in self.iter_bytecodes() if b_iter_bytecodes else [self]: + for inst in bytecode: + if inst.starts_line: + disview += f"# Line {inst.starts_line}\n" + disview += inst.get_dis_view() + "\n" + disview += "\n" + return disview + + def _change_line_number_everywhere(self, original_line_number, new_line_number): + """ + Updates all instances of original_line_number in the line_starts of instructions to become new_line_number + """ + for bc in self.iter_bytecodes(): + for inst in bc: + if inst.starts_line == original_line_number: + inst.starts_line = new_line_number + + def _patch_dummy_decorator(self, dummy_decorator_name="dummy"): + """ + Exclusively used for python <= 3,7 + Removes "@dummy_decorator_name" decorators from bytecode. + """ + for bc in self.iter_bytecodes(): + for inst in bc.instructions: + if inst.opname.startswith("LOAD") and inst.argval == dummy_decorator_name: + # target found + target_idx = bc.instructions.index(inst) + for next_inst in bc.instructions[target_idx:]: + if (next_inst.opname, next_inst.arg) == ("CALL_FUNCTION", 1): + bc.remove_instructions([next_inst]) + break + self._change_line_number_everywhere(inst.starts_line, inst.starts_line + 1) + bc.remove_instructions([inst]) + return + + def fix_while(self, source_lines): + for i in range(len(source_lines)): + if source_lines[i].startswith("while ") and source_lines[i] != "while True:": + source_lines[i] = "if" + source_lines[i][5:] + for bc in self.iter_bytecodes(): + for inst in bc: + if inst.opname.startswith("POP_JUMP_BACKWARD_IF_") or inst.opname.startswith("POP_JUMP_IF_") and inst.argval < inst.offset: + prev = bc[inst.argval // 2 - 1] + while prev.starts_line is None: + if prev.offset == 0: + prev = None + break + else: + prev = bc[prev.offset // 2 - 1] + if prev is not None and source_lines[prev.starts_line - 1].startswith("if "): + source_lines[prev.starts_line - 1] = "while" + source_lines[prev.starts_line - 1][2:] + + def fix_while12(self, source_lines): + for i in range(len(source_lines)): + if source_lines[i].startswith("while ") and source_lines[i] != "while True:": + source_lines[i] = "if" + source_lines[i][5:] + for bc in self.iter_bytecodes(): + for inst in bc: + if inst.opname == "JUMP_BACKWARD": + prev = bc[inst.offset // 2 - 1] + if prev.opname.startswith("POP_JUMP_IF") and prev.argval == inst.offset + 2: + prev = bc[inst.argval // 2 - 1] + while prev.starts_line is None: + if prev.offset == 0: + prev = None + break + else: + prev = bc[prev.offset // 2 - 1] + if prev is not None and source_lines[prev.starts_line - 1].startswith("if "): + source_lines[prev.starts_line - 1] = "while" + source_lines[prev.starts_line - 1][2:] + + def fix_format_string_lno(self): + for a in range(len(self) - 1): + if self[a + 1].starts_line is not None: + if self[a].opname == "LOAD_CONST" and type(self[a].argval) == str: + self[a].starts_line = self[a + 1].starts_line + self[a + 1].starts_line = None + + def make_absolute(self, offset, target): + return [ + self.new_instruction( + "JUMP_ABSOLUTE", + self.opcode.JUMP_ABSOLUTE, + "jabs", + instruction_size(self.opcode.JUMP_ABSOLUTE, self.opcode), + target.offset, + target.offset, + repr(target.offset), + op_has_argument(self.opcode.JUMP_ABSOLUTE, self.opcode), + offset, + None, + False, + False, + target, + ) + ] + + def make_relative(self, offset, target): + return [ + self.new_instruction( + "JUMP_FORWARD", + self.opcode.JUMP_FORWARD, + "jrel", + instruction_size(self.opcode.JUMP_FORWARD, self.opcode), + target.offset - offset, + target.offset - offset, + repr(target.offset - offset), + op_has_argument(self.opcode.JUMP_FORWARD, self.opcode), + offset, + None, + False, + False, + target, + ) + ] + + def replace_duplicated_returns10(self, source_lines): + for bc in self.iter_bytecodes(): + offsets = defaultdict(int) + if bc.codeobj.co_name in comprehension_names: + continue + for inst in bc: + if inst.starts_line is not None: + offsets[inst.starts_line] = max(offsets[inst.starts_line], inst.offset) + for inst in bc: + if inst.starts_line is not None and inst.offset != offsets[inst.starts_line]: + line = source_lines[inst.starts_line - 1].strip() + if line == "return" or line.startswith("return "): + target = bc[offsets[inst.starts_line] // 2] + bc[inst.offset // 2] = self.make_absolute(inst.offset, target) + bc.remove_unreachable_instructions() + + def replace_duplicated_returns12(self, source_lines): + for bc in self.iter_bytecodes(): + offsets = defaultdict(int) + if bc.codeobj.co_name in comprehension_names: + continue + for inst in bc: + if inst.starts_line is not None: + offsets[inst.starts_line] = max(offsets[inst.starts_line], inst.offset) + for inst in bc: + if inst.starts_line is not None and inst.offset != offsets[inst.starts_line]: + insts = [inst] + i = bc[inst.offset // 2 + 1] + while i.starts_line is None: + insts.append(i) + i = bc[i.offset // 2 + 1] + if len(insts) <= 4 and insts[-1].opname in ("RETURN_VALUE", "RETURN_CONST"): + target = bc[offsets[inst.starts_line] // 2] + bc[inst.offset // 2] = self.make_relative(inst.offset, target) + bc.remove_unreachable_instructions() + + def get_lno_insts(self, hoist_comprehensions: bool = True, previously_seen_lines: set[int] = None) -> dict[int, list[Inst]]: + """Get a dictionary that maps line numbers to sequences of bytecodes""" + if hoist_comprehensions and self.is_comprehension: + return dict() + + if previously_seen_lines is None: + previously_seen_lines = set() + + # create a dict of line num : [bytecodes composing line] + lno_bytecodes = {} + seen_lnos = set(previously_seen_lines) + current_line_bytecodes = [] + current_lno = None + + for inst in self: + if inst.starts_line is not None and inst.starts_line not in seen_lnos: + if current_line_bytecodes: + lno_bytecodes.update({current_lno: current_line_bytecodes}) + current_line_bytecodes = [] + current_lno = inst.starts_line + seen_lnos.add(current_lno) + + current_line_bytecodes.append(inst) + + if hoist_comprehensions and inst.opname == "LOAD_CONST" and getattr(inst.argval, "co_name", None) in self.bytecode_lookup: + child_code = self.co_consts[inst.arg] + if child_code.is_comprehension: + for decendant_code in child_code.iter_bytecodes(): + current_line_bytecodes.extend(decendant_code.instructions) + + # update final list + if current_line_bytecodes: + lno_bytecodes.update({current_lno: current_line_bytecodes}) + + # resolve unallocated starting instructions + if None in lno_bytecodes and len(lno_bytecodes) > 1: + unallocated_insts = lno_bytecodes.pop(None) + earliest_line = min(lno_bytecodes.keys()) + lno_bytecodes[earliest_line] = unallocated_insts + lno_bytecodes[earliest_line] + + return lno_bytecodes + + def _add_inst_exception_attrs(self): + """ + Update instruction attributes to add additional context if they are entries in the exception table + for versions >= (3,11) + """ + if self.named_exception_table is None: + return + for entry in self.named_exception_table: + self.get_by_offset(entry.start).exception_start = entry.target + self.get_by_offset(entry.end).exception_end = True + self.get_by_offset(entry.target).exception_target = True + + ## PROPERTIES + + @property + def is_comprehension(self): + return self.codeobj.co_name in comprehension_names + + @property + def first_instruction(self): + return self.instructions[0] + + ## OVERLOADS + + def __iter__(self): + return self.instructions.__iter__() + + def __getitem__(self, i): + if isinstance(i, slice): + return self.instructions[i] + + return self.instructions[i] + + def __setitem__(self, i, value): + if not isinstance(i, slice): + i = slice(i, i + 1) + insts = self.instructions[i] + if isinstance(insts, Inst): + insts = [insts] + + instruction_before = self[i.start - 1] if i.start is not None and i.start > 0 else None + instruction_after = self[i.stop] if i.stop is not None and i.stop <= len(self) else None + + for j, inst in enumerate(insts): + if isinstance(value, (list, tuple)) and len(insts) == len(value): + self._change_jump_targets(inst, value[j]) + else: + new_target = instruction_before or instruction_after + if not new_target and len(value) > 0: + new_target = value[0] + elif len(value) == 0: + pass # They should have used __del__ + + if new_target: + self._change_jump_targets(inst, new_target) + + self.instructions[i] = value + self._edited = True + + def __delitem__(self, i): + if not isinstance(i, slice): + i = slice(i, i + 1) + insts = self.instructions[i] + if isinstance(insts, Inst): + insts = [insts] + + instruction_before = self[i.start - 1] if i.start is not None and i.start > 0 else None + instruction_after = self[i.stop] if i.stop is not None and i.stop <= len(self) else None + + for inst in insts: + new_target = instruction_before or instruction_after + + if new_target: + self._change_jump_targets(inst, new_target) + + del self.instructions[i] + self._edited = True + + def __hasitem__(self, value): + return value in self.instructions + + def __len__(self): + return len(self.instructions) + + def __str__(self): + name = "" if self.name is None else self.name + "," + return self.__class__.__name__ + "<" + name + "edited=" + repr(self._edited) + ">" + pprint.pformat(self.instructions) + + def __repr__(self): + name = "" if self.name is None else self.name + "," + return self.__class__.__name__ + "<" + name + "edited=" + repr(self._edited) + ">" + f"[{len(self.instructions)} instructions,{len(self.child_bytecodes)} code objects]" diff --git a/pylingual/editable_bytecode/Instruction.py b/pylingual/editable_bytecode/Instruction.py new file mode 100644 index 0000000..fe98dcf --- /dev/null +++ b/pylingual/editable_bytecode/Instruction.py @@ -0,0 +1,266 @@ +# seperate import for type checking to prevent circular reference with EditableBytecode +from typing import TYPE_CHECKING, Optional + +from xdis import iscode +from xdis.cross_dis import instruction_size, xstack_effect +from .utils import unwrap + +if TYPE_CHECKING: + from typing import Any + from .EditableBytecode import EditableBytecode + + +class Inst: + """This is a more object-oriented, editable version of the xdis Instruction class. For use with EditableBytecode.""" + + def __init__( + self, + bytecode: "EditableBytecode", + opname: str, + opcode, + optype: str, + inst_size: int, + arg: int, + argval: "Any", + argrepr: str, + has_arg: bool, + offset: int, + starts_line: int, + is_jump_target: bool, + has_extended_arg: bool, + target: Optional["Inst"] = None, + ): + self.bytecode = bytecode + + self.opname = opname + self.opcode = opcode + self.optype = optype + self.inst_size = inst_size + self.arg = unwrap(arg) + self.argval = unwrap(argval) + self.argrepr = unwrap(argrepr) + self.has_arg = has_arg + self.original_offset = offset # We typically calculate the offset on-the-fly and cache it as necessary + self.starts_line = starts_line + self.is_jump_target = is_jump_target + self.has_extended_arg = has_extended_arg + self._target = target # Will be set by the containing EditableBytecode object + + # exception table information used for model view, values are assigned during init of parent bytecode + # only set in versions >= 3,11 + self.exception_start: int | bool = False # will either be false + self.exception_end = False + self.exception_target = False + + # dependency graph for obfuscation + self.deps = [] + self.reqs = [] + self.stack = [] + self.pop = [] + + @property + def offset(self): + return self.bytecode.get_offset(self) + + @property + def real_size(self): + return instruction_size(self.opcode, self.bytecode.opcode) + + @property + def is_jump(self): + return self.optype in ("jabs", "jrel") or self.is_cond_jump + + @property + def is_abs_jump(self): + return self.optype == "jabs" + + @property + def is_rel_jump(self): + return self.optype == "jrel" + + @property + def is_cond_jump(self): + """ + It's technically inaccurate that SETUP_WITH and SETUP_FINALLY are jumps, but they cause jumps in subsequent + instructions, so this theoretically preserves the control flow + + @return: + @rtype: + """ + + return self.opname in ( + "POP_JUMP_IF_FALSE", + "POP_JUMP_IF_TRUE", + "POP_JUMP_FORWARD_IF_FALSE", + "POP_JUMP_FORWARD_IF_TRUE", + "POP_JUMP_BACKWARD_IF_FALSE", + "POP_JUMP_BACKWARD_IF_TRUE", + "JUMP_IF_TRUE_OR_POP", + "JUMP_IF_FALSE_OR_POP", + "JUMP_IF_TRUE", + "JUMP_IF_FALSE", + "FOR_ITER", + "POP_JUMP_IF_NONE", + "POP_JUMP_IF_NOT_NONE", + "POP_JUMP_BACKWARD_IF_NONE", + "POP_JUMP_BACKWARD_IF_NOT_NONE", + "POP_JUMP_FORWARD_IF_NONE", + "POP_JUMP_FORWARD_IF_NOT_NONE", + "JUMP_IF_NOT_EXC_MATCH", + "SETUP_WITH", + "SETUP_FINALLY", + "SEND", + ) + + @property + def is_uncond_jump(self): + """ + @return: + @rtype: + """ + + return self.opcode in self.bytecode.opcode.JUMP_UNCONDITONAL # His typo, not mine + + @property + def target(self): + if not self.is_jump: + raise AttributeError("Only jump instructions have target attributes, not " + repr(self)) + return self._target + + @target.setter + def target(self, value): + if not self.is_jump: + raise AttributeError("Only jump instructions have target attributes, not " + repr(self)) + self._target = value + + def add_reqs(self, *reqs): + for r in reqs: + if isinstance(r, tuple): + r = r[0] + if isinstance(r, Inst): + r.deps.append(self) + self.reqs.append(r) + + @property + def next_instructions(self): + """Returns the instruction(s) that follow this one, including a jump target (if any).""" + next = [] + if self.is_jump: + next.append(self.target) + if not self.is_uncond_jump and self.opname not in ("RETURN_VALUE", "RAISE_VARARGS", "RETURN_CONST"): + inst_after = self.bytecode._get_instruction_after(self) + if inst_after: + next.append(inst_after) + if self.offset in self.bytecode.exception_table: + target_offset = self.bytecode.exception_table[self.offset][1] + next.append(self.bytecode[target_offset // 2]) + + return next + + @property + def next_instructions_basic(self): + """Returns the next instruction(s), IGNORING control flow from exceptions""" + + if self.opname in ("SETUP_WITH", "SETUP_FINALLY"): + inst_after = self.bytecode._get_instruction_after(self) + return [inst_after] if inst_after else [] + + next = [] + if self.is_jump: + next.append(self.target) + if not self.is_uncond_jump: + inst_after = self.bytecode._get_instruction_after(self) + if inst_after: + next.append(inst_after) + + return next + + def get_next_instructions(self, follow_uncond_jumps: bool = False): + """ + A slightly more robust version of the property "next_instructions" that allows for following unconditional + jumps. + + @param follow_uncond_jumps: + @type follow_uncond_jumps: + @return: + @rtype: + """ + next_instructions = set(self.next_instructions) + last_next = next_instructions + + while follow_uncond_jumps: + new_next = set() + + for inst in last_next: + if (inst.is_jump and not inst.is_cond_jump) or inst.opcode == self.bytecode.opcode.EXTENDED_ARG: + new_next.update(inst.next_instructions) + + before = len(next_instructions) + next_instructions.update(new_next) + + if len(next_instructions) > before: + last_next = new_next + else: + break + + return next_instructions + + def get_stack_effect(self, jump: bool = True): + """Returns the "effect" this instruction will have on the stack. May occasionally return None?""" + return xstack_effect(self.opcode, self.bytecode.opcode, oparg=self.arg, jump=jump) + + ### DIS VIEWS ### + + @property + def jumped_to_from_insts(self) -> list["Inst"]: + if not self.is_jump_target: + return [] + + return [inst for inst in self.bytecode if hasattr(inst, "target") and self is inst.target] + + def get_dis_view(self) -> str: + """Get a dissassembled view of the instruction, similar to that of dis output""" + if iscode(self.argval): + argrepr = f"code object {self.argval.co_name}" + else: + argrepr = self.argrepr + return f"{self.offset} {self.opname}{' ' + str(self.arg) if self.has_arg else ''}{' (' + argrepr + ')' if argrepr else ''}" + + ##### + + def __repr__(self): + attr_list = ( + "opname", + "opcode", + "optype", + "real_size", + "arg", + "argval", + "argrepr", + "has_arg", + "offset", + "starts_line", + "is_jump_target", + "has_extended_arg", + ) + + return self.__class__.__name__ + "(" + ", ".join((attr + "=" + repr(getattr(self, attr))) for attr in attr_list) + ")" + + @classmethod + def from_instruction(self, bytecode, inst): + """Creates an Inst from an xdis Instruction object""" + return Inst( + bytecode, + inst.opname, + inst.opcode, + inst.optype, + inst.inst_size, + inst.arg, + inst.argval, + inst.argrepr, + inst.has_arg, + inst.offset, + inst.starts_line, + inst.is_jump_target, + inst.has_extended_arg, + ) diff --git a/pylingual/editable_bytecode/PYCFile.py b/pylingual/editable_bytecode/PYCFile.py new file mode 100644 index 0000000..fea304c --- /dev/null +++ b/pylingual/editable_bytecode/PYCFile.py @@ -0,0 +1,84 @@ +from pylingual.utils.version import PythonVersion +from .EditableBytecode import EditableBytecode +from .utils import write_pyc + +from xdis.load import load_module_from_file_object, load_module +import xdis.opcodes + +from io import BytesIO +import pathlib + + +class PYCFile(EditableBytecode): + """Represents a .pyc file. Upon creation, extracts all the bytecode from it.""" + + def __init__(self, source, name_prefix=None): + self.pyc_path = None + source_tuple = (None, None, None, None, None, None, None) + if isinstance(source, bytes): + source = BytesIO(source) + source_tuple = load_module_from_file_object(source) + elif isinstance(source, pathlib.Path): + source_tuple = load_module(str(source)) + self.pyc_path = source + elif source is not None: + source_tuple = load_module(source) + + ( + version, + self.timestamp, + self.magic, + self.code, + self.ispypy, + self.source_size, + self.sip_hash, + ) = source_tuple + + self.version = PythonVersion(version) + opcode = getattr(xdis.opcodes, f"opcode_{self.version[0]}{self.version[1]}") + + EditableBytecode.__init__( + self, + self.code, + opcode, + self.version, + name_prefix=name_prefix, + ) + + def copy(self): + try: + copy = PYCFile(None) + EditableBytecode.__init__(copy, self.to_code(), self.opcode, self.version, self.name_prefix, False) + except IndexError: + copy = EditableBytecode.copy(self) + + for attr in ( + "version", + "timestamp", + "magic", + "code", + "ispypy", + "source_size", + "sip_hash", + ): + setattr(copy, attr, getattr(self, attr)) + + return copy + + def save(self, file, should_close=True, no_lnotab=False): + """Saves the current recursive bytecode to the specified file.""" + if isinstance(file, str): + file = open(file, "wb") + + write_pyc( + file, + self.to_code(no_lnotab=no_lnotab), + self.version, + self.magic, + self.timestamp, + self.source_size, + ) + + if should_close: + file.close() + return file diff --git a/pylingual/editable_bytecode/__init__.py b/pylingual/editable_bytecode/__init__.py new file mode 100644 index 0000000..663ba8e --- /dev/null +++ b/pylingual/editable_bytecode/__init__.py @@ -0,0 +1,7 @@ +from .EditableBytecode import EditableBytecode +from .Instruction import Inst +from .PYCFile import PYCFile + +import pylingual.editable_bytecode.bytecode_patches + +__all__ = ["EditableBytecode", "Inst", "PYCFile"] diff --git a/pylingual/editable_bytecode/bytecode_patches/__init__.py b/pylingual/editable_bytecode/bytecode_patches/__init__.py new file mode 100644 index 0000000..4499de5 --- /dev/null +++ b/pylingual/editable_bytecode/bytecode_patches/__init__.py @@ -0,0 +1,5 @@ +from .fix_unreachable import fix_unreachable +from .remove_extended_arg import remove_extended_arg +from .remove_docstrings import remove_docstrings +from .remove_nop import remove_nop +from .fix_indirect_jump import fix_indirect_jump diff --git a/pylingual/editable_bytecode/bytecode_patches/fix_indirect_jump.py b/pylingual/editable_bytecode/bytecode_patches/fix_indirect_jump.py new file mode 100644 index 0000000..c18c319 --- /dev/null +++ b/pylingual/editable_bytecode/bytecode_patches/fix_indirect_jump.py @@ -0,0 +1,12 @@ +from ..EditableBytecode import EditableBytecode + + +def fix_indirect_jump(bytecode: EditableBytecode): + for i in bytecode: + if i.is_jump: + # avoid infinite loop + limit = 99 + while i.target.is_uncond_jump and limit: + i.argval = i.target.argval + i.target = i.target.target + limit -= 1 diff --git a/pylingual/editable_bytecode/bytecode_patches/fix_unreachable.py b/pylingual/editable_bytecode/bytecode_patches/fix_unreachable.py new file mode 100644 index 0000000..804ec30 --- /dev/null +++ b/pylingual/editable_bytecode/bytecode_patches/fix_unreachable.py @@ -0,0 +1,7 @@ +from ..EditableBytecode import EditableBytecode + + +def fix_unreachable(bytecode: EditableBytecode): + bytecode.remove_unreachable_instructions() + bytecode.remove_useless_jumps() + bytecode._bake_jumps() diff --git a/pylingual/editable_bytecode/bytecode_patches/remove_docstrings.py b/pylingual/editable_bytecode/bytecode_patches/remove_docstrings.py new file mode 100644 index 0000000..e2aa4a3 --- /dev/null +++ b/pylingual/editable_bytecode/bytecode_patches/remove_docstrings.py @@ -0,0 +1,9 @@ +from ..EditableBytecode import EditableBytecode + +import itertools + + +def remove_docstrings(bytecode: EditableBytecode): + to_remove = [(load_const, store_doc) for load_const, store_doc in itertools.pairwise(bytecode.instructions) if load_const.opname == "LOAD_CONST" and store_doc.opname == "STORE_NAME" and store_doc.argval == "__doc__"] + to_remove = list(itertools.chain.from_iterable(to_remove)) + bytecode.remove_instructions(to_remove) diff --git a/pylingual/editable_bytecode/bytecode_patches/remove_extended_arg.py b/pylingual/editable_bytecode/bytecode_patches/remove_extended_arg.py new file mode 100644 index 0000000..43d8c1b --- /dev/null +++ b/pylingual/editable_bytecode/bytecode_patches/remove_extended_arg.py @@ -0,0 +1,8 @@ +from ..EditableBytecode import EditableBytecode + + +def remove_extended_arg(bytecode: EditableBytecode): + for i in bytecode: + i.has_extended_arg = False + to_remove = [x for x in bytecode.instructions if x.opname == "EXTENDED_ARG"] + bytecode.remove_instructions(to_remove) diff --git a/pylingual/editable_bytecode/bytecode_patches/remove_nop.py b/pylingual/editable_bytecode/bytecode_patches/remove_nop.py new file mode 100644 index 0000000..ce3505b --- /dev/null +++ b/pylingual/editable_bytecode/bytecode_patches/remove_nop.py @@ -0,0 +1,6 @@ +from ..EditableBytecode import EditableBytecode + + +def remove_nop(bytecode: EditableBytecode): + to_remove = [x for x in bytecode.instructions if x.opname == "NOP"] + bytecode.remove_instructions(to_remove) diff --git a/pylingual/editable_bytecode/control_flow_graph.py b/pylingual/editable_bytecode/control_flow_graph.py new file mode 100644 index 0000000..c0287fc --- /dev/null +++ b/pylingual/editable_bytecode/control_flow_graph.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +from .Instruction import Inst + +import networkx as nx +from enum import Enum + + +class ControlFlowEdgeType(Enum): + NATURAL = "natural" # edge goes to the next instruction in sequence; this is the default + JUMP = "jump" # edge represents an unconditional jump + TRUE_JUMP = "true_jump" # edge represents a conditional jump that is taken when the condition is true + FALSE_JUMP = "false_jump" # edge represents a conditional jump that is taken when the condition is false + EXCEPTION = "exception" # edge goes to an exception handler + META = "meta" # used exclusively for the START and END meta-nodes that are added to the cfg + + +from typing import Any + + +def inst_to_node_attributes(inst: "Inst") -> dict: + return {"label": inst.get_dis_view()} + + +def inst_to_successors(inst: "Inst") -> list[tuple["Inst", ControlFlowEdgeType]]: + # premature exits + if inst.opname in ("RETURN_VALUE", "RETURN_CONST", "RAISE_VARARGS", "RERAISE"): + return [] + + successors = [] + # jump target + if inst.is_jump and inst.opname not in ("SETUP_WITH", "SETUP_ASYNC_WITH", "SETUP_FINALLY", "SETUP_CLEANUP", "SETUP_EXCEPT"): + edge_type = ControlFlowEdgeType.JUMP + if inst.is_cond_jump and any(s in inst.opname for s in ("IF_FALSE", "IF_NONE", "FOR_ITER", "IF_NOT_EXC_MATCH")): + edge_type = ControlFlowEdgeType.FALSE_JUMP + elif inst.is_cond_jump and ("IF_TRUE" in inst.opname or "IF_NOT_NONE" in inst.opname): + edge_type = ControlFlowEdgeType.TRUE_JUMP + elif inst.opname == "SEND": + edge_type = ControlFlowEdgeType.TRUE_JUMP + successors.append((inst.target, edge_type)) + + # regular follow-up instruction + if not inst.is_uncond_jump: + next_instruction = inst.bytecode._get_instruction_after(inst) + if next_instruction: + successors.append((next_instruction, ControlFlowEdgeType.NATURAL)) + + return successors + + +# returns a list of tuples of the form +# (source, destination, edge_properties, next_state) +def inst_to_edges_37(inst: "Inst", state: Any) -> list[tuple["Inst", "Inst", dict[str, ControlFlowEdgeType], Any]]: + if state is None: + block_stack = tuple() + else: + block_stack = state + + # update exception handler (pre-3.11 style) + if inst.opname in ("SETUP_FINALLY", "SETUP_WITH", "SETUP_ASYNC_WITH", "SETUP_CLEANUP", "SETUP_EXCEPT"): + # add new exception handler to the stack + next_block_stack = (*block_stack, (inst.target, "exception")) + elif inst.opname in ("POP_BLOCK",): + # remove an exception handler from the stack + next_block_stack = block_stack[:-1] + elif inst.opname in ("SETUP_LOOP",): + next_block_stack = (*block_stack, (inst.target, "loop")) + else: + # no change to the exception handler + next_block_stack = block_stack + + if inst.opname in ("BREAK_LOOP", "CONTINUE_LOOP"): + # get the highest loop block from the block stack; default to None + def is_loop_block(block: tuple["Inst", str]) -> bool: + return block[1] == "loop" + + break_target = next(filter(is_loop_block, reversed(block_stack)), None) + loop_block_index = block_stack.index(break_target) + block_stack = block_stack[:loop_block_index] + # after breaking out of the loop or continuing, pop off any exeption blocks leading up to the loop block + if inst.opname == "BREAK_LOOP": + edges = [((inst, break_target[0], ControlFlowEdgeType.JUMP), block_stack)] + elif inst.opname == "CONTINUE_LOOP": + # CONTINUE_LOOP has the offset of the loop instruction as the argval; go there to continue + edges = [((inst, inst.bytecode.get_by_offset(inst.argval), ControlFlowEdgeType.JUMP), block_stack)] + else: + # list of tuples [(successor, edge_type) ...] + successors = inst_to_successors(inst) + edges = [((inst, successor, edge_type), next_block_stack) for successor, edge_type in successors] + + # get the highest exception block from the block stack; default to None + def is_exception_block(block: tuple["Inst", str]) -> bool: + return block[1] == "exception" + + exception_target = next(filter(is_exception_block, reversed(block_stack)), None) + if exception_target: + exception_block_index = block_stack.index(exception_target) + edges.append(((inst, exception_target[0], ControlFlowEdgeType.EXCEPTION), block_stack[:exception_block_index])) + + return edges + + +def inst_to_edges_39(inst: "Inst", state: Any) -> list[tuple[tuple["Inst", "Inst", dict[str, ControlFlowEdgeType]], Any]]: + if state is None: + block_stack = tuple() + else: + block_stack = state + + exception_target = block_stack[-1] if block_stack else None + + # update exception handler (pre-3.11 style) + if inst.opname in ("SETUP_FINALLY", "SETUP_WITH", "SETUP_ASYNC_WITH", "SETUP_CLEANUP"): + # add new exception handler to the stack + next_block_stack = (*block_stack, inst.target) + elif inst.opname in ("POP_BLOCK"): + # remove an exception handler from the stack + next_block_stack = block_stack[:-1] + else: + # no change to the exception handler + next_block_stack = block_stack + + # list of tuples [(successor, edge_type) ...] + successors = inst_to_successors(inst) + edges = [((inst, successor, edge_type), next_block_stack) for successor, edge_type in successors] + + if exception_target: + edges.append(((inst, exception_target, ControlFlowEdgeType.EXCEPTION), block_stack[:-1])) + + return edges + + +def inst_to_edges_311(inst: "Inst", state: Any) -> list[tuple[tuple["Inst", "Inst", dict[str, ControlFlowEdgeType]], Any]]: + exception_table = inst.bytecode.named_exception_table + # search the exception table for any entry that applies to the current instruction + exception_target = None + for exception_range in exception_table: + if inst.offset >= exception_range.start and inst.offset < exception_range.end: + exception_target = inst.bytecode.get_by_offset(exception_range.target) + break + + # list of tuples [(successor, edge_type) ...] + successors = inst_to_successors(inst) + edges = [((inst, successor, edge_type), None) for successor, edge_type in successors] + + if exception_target: + edges.append(((inst, exception_target, ControlFlowEdgeType.EXCEPTION), None)) + + return edges + + +def bytecode_to_control_flow_graph(bytecode: "EditableBytecode") -> nx.DiGraph: + cfg = nx.DiGraph() + + # add one node for each instruction + cfg.add_nodes_from((inst, inst_to_node_attributes(inst)) for inst in bytecode) + + # depth first traversal of the bytecode + visited_instructions = set() + # dfs stack contains (instruction, inst_to_edges_state) + dfs_stack = [(bytecode.first_instruction, None)] + + inst_to_edges = inst_to_edges_311 + if bytecode.version < (3, 11): + inst_to_edges = inst_to_edges_39 + if bytecode.version < (3, 8): + inst_to_edges = inst_to_edges_37 + + while dfs_stack: + # boilerplate depth-first traversal + current_inst, current_state = dfs_stack.pop() + if current_inst in visited_instructions: + continue + visited_instructions.add(current_inst) + + edges = inst_to_edges(current_inst, current_state) + for (src, dst, edge_type), next_state in edges: + cfg.add_edge(src, dst, type=edge_type.value) + dfs_stack.append((dst, next_state)) + + # remove unreachable instructions + cfg.remove_nodes_from(set(bytecode) - visited_instructions) + + return cfg diff --git a/pylingual/editable_bytecode/utils.py b/pylingual/editable_bytecode/utils.py new file mode 100644 index 0000000..73566cf --- /dev/null +++ b/pylingual/editable_bytecode/utils.py @@ -0,0 +1,89 @@ +import re +from datetime import datetime +from struct import pack + +from xdis.cross_types import LongTypeForPython3, UnicodeForPython3 +import xdis.marsh as marshal +from xdis import iscode +from xdis.codetype import to_portable + +from pylingual.utils.version import PythonVersion + + +def unwrap(x): + """ + Turns xdis-specific types back into Python types + """ + if isinstance(x, UnicodeForPython3): + return str(x) + if isinstance(x, LongTypeForPython3): + return int(x) + if isinstance(x, tuple): + return tuple(unwrap(e) for e in x) + if isinstance(x, list): + return [unwrap(e) for e in x] + return x + + +def codeobj_replace(codeobj, **kwargs): + if hasattr(codeobj, "replace"): + return codeobj.replace(**kwargs) + else: + all_kwargs = {key: getattr(codeobj, key) for key in dir(codeobj) if key.startswith("co_")} + all_kwargs.update(kwargs) + + return to_portable(**all_kwargs) + + +def write_pyc(f, codeobj, version, magic_int, timestamp=None, filesize=0): + """ + Mostly taken from xdis.load's write_bytecode_file function. + Does not close the provided fileobject upon return. + """ + + version = PythonVersion(version) + if version >= (3, 0): + f.write(pack("= (3, 7): # pep552 bytes + f.write(pack("= (3, 3): + # In Python 3.3+, these 4 bytes are the size of the source code_obj file (mod 2^32) + f.write(pack("", + "", + "", + "", + "", +) diff --git a/pylingual/equivalence_check.py b/pylingual/equivalence_check.py new file mode 100644 index 0000000..45e8621 --- /dev/null +++ b/pylingual/equivalence_check.py @@ -0,0 +1,218 @@ +from __future__ import annotations + +import difflib +from dataclasses import dataclass +from pathlib import Path + +import networkx as nx +from pylingual.control_flow_reconstruction.structure_control_flow import condense_basic_blocks +from pylingual.editable_bytecode import EditableBytecode, Inst, PYCFile +from pylingual.editable_bytecode.bytecode_patches import fix_indirect_jump, fix_unreachable, remove_extended_arg, remove_nop +from pylingual.editable_bytecode.control_flow_graph import bytecode_to_control_flow_graph + + +def is_control_flow_equivalent(basic_block_graph_1: nx.DiGraph, basic_block_graph_2: nx.DiGraph) -> bool: + # for the graphs to be equal, they have to have the same number of nodes + + if len(basic_block_graph_1) != len(basic_block_graph_2): + return False + + # create a node mapping based on bytecode offset + # each node is (networkx node id, data_item); in this case, the data_item will be offset + ordered_nodes_1 = sorted(basic_block_graph_1.nodes(data="offset", default=(float("inf"),)), key=lambda node: min(node[1])) + ordered_nodes_2 = sorted(basic_block_graph_2.nodes(data="offset", default=(float("inf"),)), key=lambda node: min(node[1])) + + # map the node ids from graph 1 to those of graph 2 + node_id_mapping = {node1[0]: node2[0] for node1, node2 in zip(ordered_nodes_1, ordered_nodes_2)} + + for node1, edges1 in basic_block_graph_1.adjacency(): + # for each node in graph 1, find the corresponding node in graph 2 + node2 = node_id_mapping[node1] + edges2 = basic_block_graph_2[node2] + + # map the graph 1 edge destinations to use graph 2 node ids + # make a set of all the destinations for this node pair + mapped_destinations1 = set(node_id_mapping[dest] for dest in edges1.keys()) + destinations2 = set(edges2.keys()) + + # if the outgoing edges don't match, then the control flow is not equivalent + if mapped_destinations1 != destinations2: + return False + + # all checks passed; these graphs are equivalent + return True + + +def compare_instruction(inst_a: Inst, inst_b: Inst): + attr_list = ("opname", "opcode", "optype", "real_size", "has_arg", "has_extended_arg", "offset", "is_jump_target") + + # resolve different types of uncond jumps + if inst_a.is_uncond_jump and inst_b.is_uncond_jump: + return getattr(inst_a, "argval", None) == getattr(inst_b, "argval", None) + + if not all(getattr(inst_a, attr, None) == getattr(inst_b, attr, None) for attr in attr_list): + return False + + argval_a = getattr(inst_a, "argval", None) + argval_b = getattr(inst_b, "argval", None) + + if argval_a == argval_b: + return True + + if hasattr(argval_a, "co_code") and hasattr(argval_b, "co_code"): + # the co_code's will be checked in recursive call. so can be ignored here + return True + + return False + + +def compare_bytecode(pyc_a: EditableBytecode, pyc_b: EditableBytecode) -> bcComparisonResult: + """ + Directly Compares two pyc files by recursing through root bytecode and all child bytecodes of pyc files + Ignores some forensically irrelevant data such as: + white space / line differences, + Code_obj addresses + + :param pyc_a: First pyc to compare + :param pyc_b: Second pyc to compare + """ + + if len(pyc_a) != len(pyc_b): + return bcComparisonResult(False) + + if pyc_a.named_exception_table != pyc_b.named_exception_table: + return bcComparisonResult(False) + + # make sure all the instructions match at this node + + for inst_a, inst_b in zip(pyc_a, pyc_b): + if not compare_instruction(inst_a, inst_b): + # We purposefully check pyc_b as this is our candidate pyc in decompiler.py + fail_offset = inst_b.offset + insts_b = pyc_b.instructions + inst_idx = insts_b.index(inst_b) + try: + lno = next(inst.starts_line for inst in reversed(insts_b[:inst_idx]) if inst.starts_line is not None) + except StopIteration: + lno = None + return bcComparisonResult(False, lno, fail_offset) + return bcComparisonResult(True) + + +@dataclass(frozen=True) +class TestResult: + """ + This class stores the testing result of a code object, if the code object succeeds success will be true. + If the code object does not succeed success will be false, additionally the error message and line number is saved. + + :param success: Stores whether the code object succeeded or failed. + :param message: Error message if the code object failed + :param name_a: Code object name of bytecode a + :param name_b: Code object name of bytecode b + :param failed_line_number: The line number where the code object failed + :param failed offset: The offset where the code object failed + """ + + success: bool + message: str + name_a: str + name_b: str + failed_line_number: int | None = None + failed_offset: int | None = None + + def names(self): + if self.name_a == self.name_b: + return self.name_a + return f"{self.name_a}, {self.name_b}" + + def __str__(self): + if self.success: + return f"{self.names()}: Success: {self.message}" + lno_message = "" + if None not in (self.failed_line_number, self.failed_offset): + lno_message = f" detected at line number {self.failed_line_number} and instruction offset {self.failed_offset}" + return f"***{self.names()}: Failure{lno_message}: {self.message}" + + +@dataclass(frozen=True) +class bcComparisonResult: + result: bool + failed_line: int | None = None + failed_offset: int | None = None + + +def matching_iter(pyc_a, pyc_b): + """ + Matches bytecodes in pyc_a and pyc_b with the same name + """ + bc_a = list(pyc_a.iter_bytecodes()) + bc_b = list(pyc_b.iter_bytecodes()) + sm = difflib.SequenceMatcher(a=[x.name for x in bc_a], b=[x.name for x in bc_b]) + i_a = 0 + i_b = 0 + for block in sm.get_matching_blocks(): + while i_a < block.a: + yield bc_a[i_a], None + i_a += 1 + while i_b < block.b: + yield None, bc_b[i_b] + i_b += 1 + for i in range(block.size): + yield bc_a[i_a + i], bc_b[i_b + i] + i_a += block.size + i_b += block.size + while i_a < len(bc_a): + yield bc_a[i_a], None + i_a += 1 + while i_b < len(bc_b): + yield None, bc_b[i_b] + i_b += 1 + + +def compare_pyc(pyc_path_a: Path, pyc_path_b: Path) -> list[TestResult]: + """ + Tests the control flow of the two pyc files + Should not be imported as it relies on TestResult class. + + note: will always patch out unreachable code + + :param pyc_path_a: First pyc to compare + :param pyc_path_b: Second pyc to compare + """ + + pyc_a = PYCFile(pyc_path_a) + pyc_b = PYCFile(pyc_path_b) + + pyc_a.apply_patches([remove_extended_arg, remove_nop, fix_indirect_jump, fix_unreachable, remove_extended_arg]) + pyc_b.apply_patches([remove_extended_arg, remove_nop, fix_indirect_jump, fix_unreachable, remove_extended_arg]) + + results = [] + + for bytecode_a, bytecode_b in matching_iter(pyc_a, pyc_b): + if bytecode_a is None: + test_result = TestResult(False, "Extra bytecode", "None", bytecode_b.name) + results.append(test_result) + continue + if bytecode_b is None: + test_result = TestResult(False, "Missing bytecode", bytecode_a.name, "None") + results.append(test_result) + continue + cfg_a = bytecode_to_control_flow_graph(bytecode_a) + cfg_b = bytecode_to_control_flow_graph(bytecode_b) + block_graph_a = condense_basic_blocks(cfg_a) + block_graph_b = condense_basic_blocks(cfg_b) + if not is_control_flow_equivalent(block_graph_a, block_graph_b): + test_result = TestResult(False, "Different control flow", bytecode_a.name, bytecode_b.name) + results.append(test_result) + continue + + bytecode_result = compare_bytecode(bytecode_a, bytecode_b) + if not bytecode_result.result: + test_result = TestResult(False, "Different bytecode", bytecode_a.name, bytecode_b.name, bytecode_result.failed_line, bytecode_result.failed_offset) + results.append(test_result) + continue + + test_result = TestResult(True, "Equal", bytecode_a.name, bytecode_b.name) + results.append(test_result) + + return results diff --git a/pylingual/main.py b/pylingual/main.py new file mode 100644 index 0000000..d45a47f --- /dev/null +++ b/pylingual/main.py @@ -0,0 +1,164 @@ +from typing import TYPE_CHECKING +import click +import logging +import shutil +import platform +import subprocess +import os +from pathlib import Path + +import pylingual.utils.ascii_art as ascii_art +from pylingual.utils.generate_bytecode import CompileError +from pylingual.utils.version import PythonVersion, supported_versions +from pylingual.utils.tracked_list import TrackedList, SEGMENTATION_STEP, TRANSLATION_STEP, CFLOW_STEP, CORRECTION_STEP +from pylingual.utils.lazy import lazy_import +from pylingual.decompiler import DecompilerResult, decompile + +import rich +from rich.align import Align +from rich.console import Group +from rich.live import Live +from rich.logging import RichHandler +from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn +from rich.rule import Rule +from rich.status import Status +from rich.theme import Theme +from rich.table import Table + +if TYPE_CHECKING: + import transformers +else: + lazy_import("transformers") + +logger = logging.getLogger(__name__) + + +def print_header(): + console = rich.get_console() + console.rule() + console.print(Align(ascii_art.PYLINGUAL_ART, "center"), style="royal_blue1", highlight=False) + console.print(ascii_art.PYLINGUAL_SUBHEADER, justify="center") + console.rule() + + +def print_result(file: str, result: DecompilerResult): + table = Table(title=f"Equivalence Results for {file}") + table.add_column("Code Object") + table.add_column("Success") + table.add_column("Message") + for r in result.equivalence_results: + if isinstance(r, CompileError): + continue + table.add_row(r.names(), "Success" if r.success else "Failure", r.message, style="red" if not r.success else "") + if table.rows: + rich.get_console().print(table, justify="center") + + +@click.command(help="End to end pipeline to decompile Python bytecode into source code.", context_settings={"help_option_names": ["-h", "--help"]}) +@click.argument("files", nargs=-1) +@click.option("-o", "--out-dir", default=None, type=Path, help="The directory to export results to.", metavar="PATH") +@click.option("-c", "--config-file", default=None, type=Path, help="Config file for model information.", metavar="PATH") +@click.option("-v", "--version", default=None, type=PythonVersion, help="Python version of the .pyc, default is auto detection.", metavar="VERSION") +@click.option("-k", "--top-k", default=10, type=int, help="Maximum number of additional segmentations to consider.", metavar="INT") +@click.option("-q", "--quiet", is_flag=True, default=False, help="Suppress console output.") +@click.option("--trust-lnotab", is_flag=True, default=False, help="Use the lnotab for segmentation instead of the segmentation model.") +@click.option("--init-pyenv", is_flag=True, default=False, help="Install pyenv before decompiling.") +def main(files: list[str], out_dir: Path | None, config_file: Path | None, version: PythonVersion | None, top_k: int, trust_lnotab: bool, init_pyenv: bool, quiet: bool): + rich.reconfigure(markup=False, emoji=False, quiet=quiet, theme=Theme({"logging.keyword": "yellow not bold"})) + console = rich.get_console() + log_handler = RichHandler(console=console, rich_tracebacks=True) + logging.basicConfig(level="INFO", format="%(message)s", datefmt="[%X]", handlers=[log_handler], force=True) + + if not init_pyenv and not files: + click.echo(click.get_current_context().get_help()) + return + + print_header() + + if init_pyenv and (not install_pyenv() or not files): + return + + if out_dir is not None: + out_dir.mkdir(parents=True, exist_ok=True) + + progress = Progress( + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TaskProgressColumn(), + TimeElapsedColumn(), + ) + status = Status("Initializing...") + + # extend TrackedList to update progress bars + def init(self): + self.task = next(x for x in progress.tasks if x.description == self.name) + self.task.total = len(self.x) + 1 + progress.start_task(self.task.id) + + TrackedList.init = init + TrackedList.progress = lambda self, i: progress.advance(self.task.id, i) + # the step is not done until the TrackedList is deleted + TrackedList.__del__ = lambda self: progress.advance(self.task.id, float("inf")) + + n = len(files) + with Live(Group(Rule(), status, progress), transient=True, console=console, refresh_per_second=12.5): + transformers.logging.disable_default_handler() + transformers.logging.add_handler(log_handler) + progress.add_task(SEGMENTATION_STEP, start=False) + progress.add_task(TRANSLATION_STEP, start=False) + progress.add_task(CFLOW_STEP, start=False) + progress.add_task(CORRECTION_STEP, start=False) + for i, file in enumerate(files): + for task in progress.tasks: + progress.reset(task.id, start=False) + pyc_path = Path(file) + log_handler.keywords = [file, pyc_path.name, pyc_path.with_suffix(".py").name] + status.update(f"Decompiling {pyc_path} ({i + 1} / {n})") + if not pyc_path.exists(): + raise FileNotFoundError(f"pyc file {pyc_path} does not exist") + + try: + result = decompile( + file=pyc_path, + out_dir=out_dir / f"decompiled_{pyc_path.stem}" if out_dir is not None else Path(f"decompiled_{pyc_path.stem}"), + config_file=Path(config_file) if config_file else None, + version=version, + top_k=top_k, + trust_lnotab=trust_lnotab, + ) + print_result(pyc_path.name, result) + except Exception: + logger.exception(f"Failed to decompile {pyc_path}") + console.rule() + + +def install_pyenv(): + if shutil.which("pyenv") is not None: + logger.warning("pyenv seems to already be installed, ignoring --init-pyenv...") + return True + if platform.system() not in ["Linux", "Darwin"] and not click.confirm("pyenv is probably not supported on your operating system. Continue?", default=False): + return False + cmd = "curl -fsSL https://pyenv.run | bash" + if not click.confirm(f"pyenv will be installed with the following command:\n\n\t{cmd}\n\nContinue?", default=True): + return False + if subprocess.run(cmd, shell=True).returncode != 0: + logger.error("pyenv install failed, exiting...") + return False + os.environ["PATH"] = f"{os.environ.get('PYENV_ROOT', os.path.expanduser('~/.pyenv'))}/bin:{os.environ['PATH']}" + if shutil.which("pyenv") is None: + logger.error("Could not find pyenv, exiting...") + return False + versions = click.prompt( + "Enter comma-separated Python versions to install (leave empty to install all supported versions)", + value_proc=lambda s: [PythonVersion(x) for x in s.split(",")] if isinstance(s, str) else s, + default=supported_versions, + show_default=False, + ) + if subprocess.run(["pyenv", "install", *map(str, versions)]).returncode != 0: + logger.error("Error installing Python versions, exiting...") + return False + return True + + +if __name__ == "__main__": + main() diff --git a/pylingual/masking/ast_masker.py b/pylingual/masking/ast_masker.py new file mode 100644 index 0000000..1e2fe8d --- /dev/null +++ b/pylingual/masking/ast_masker.py @@ -0,0 +1,327 @@ +import ast +import re +import copy +from pylingual.masking.global_masker import Masker +from pylingual.utils.version import PythonVersion + + +class customUnparser(ast._Unparser): + def __init__(self, masker: Masker, **kwargs): + ast._Unparser.__init__(self, **kwargs) + self.masker = masker + + def visit_Constant(self, node): + value = node.value + if isinstance(value, tuple): + with self.delimit("(", ")"): + self.items_view(self._write_constant, value) + elif value is ...: + self.write("...") + else: + if value in self.masker.global_tab.values(): + # get key from value in dictionary + key = self.masker.unmask(value) + if not isinstance(key, str): + self.write(value) + return + self._write_constant(value) + + def visit_FormattedValue(self, node): + def unparse_inner(inner): + unparser = type(self)(self.masker, _avoid_backslashes=True) + unparser.set_precedence(ast._Precedence.TEST.next(), inner) + return unparser.visit(inner) + + with self.delimit("{", "}"): + expr = unparse_inner(node.value) + if "\\" in expr: + raise ValueError("Unable to avoid backslash in f-string expression part") + if expr.startswith("{"): + # Separate pair of opening brackets as "{ {" + self.write(" ") + self.write(expr) + if node.conversion != -1: + self.write(f"!{chr(node.conversion)}") + if node.format_spec: + self.write(":") + self._write_fstring_inner(node.format_spec) + + +def custom_unparse(ast_obj, masker: Masker): + unparser = customUnparser(masker) + return unparser.visit(ast_obj) + + +def evaluate_binop_optimizations(node: ast.BinOp, version): + def eval_expr_binop(binop): + if ast.BinOp in (type(binop.left), type(binop.right)): + if type(binop.left) == ast.BinOp: + binop.left = eval_expr_binop(binop.left) + if type(binop.right) == ast.BinOp: + binop.right = eval_expr_binop(binop.right) + + if ast.UnaryOp in (type(binop.left), type(binop.right)): + if type(binop.left) == ast.UnaryOp: + binop.left = evaluate_unaryop_optimizations(binop.left, version) + if type(binop.right) == ast.UnaryOp: + binop.right = evaluate_unaryop_optimizations(binop.right, version) + + if type(binop.left) == type(binop.right) == ast.Constant: + # don't simplify if the calculation throws an error + try: + value = eval(compile(ast.fix_missing_locations(ast.Expression(binop)), "", "eval")) + except (TypeError, ZeroDivisionError): + return binop + # following two checks are for catching cases where the compiler will not premptively evaluate the expression + # see max sizes in cpython: https://github.com/python/cpython/blob/34e93d3998bab8acd651c50724eb1977f4860a08/Python/ast_opt.c#LL156C1-L156C1 + if type(value) in (str, bytes): + if len(value) > 20 and version == (3, 6) and type(binop.op) == ast.Mult: + return binop + if len(value) > 4096: + return binop + elif type(value) == int: + if len(bin(value)[2:]) > 128: + return binop + + # % style string formatting is not optimized by the compiler + if type(binop.left.value) == str and type(binop.right.value) == str and type(binop.op) == ast.Mod: + return binop + return ast.Constant(value=value) + + return binop + + return eval_expr_binop(node) + + +def evaluate_unaryop_optimizations(node: ast.UnaryOp, version): + def eval_expr_unaryop(unaryop: ast.UnaryOp): + if type(unaryop.operand) == ast.UnaryOp: + unaryop.operand = eval_expr_unaryop(unaryop.operand) + + if type(unaryop.operand) == ast.BinOp: + unaryop.operand = evaluate_binop_optimizations(unaryop.operand, version) + + if type(unaryop.operand) == ast.Constant: + # don't simplify if the calculation throws an error + try: + value = eval(ast.unparse(unaryop)) + except TypeError: + return unaryop + return ast.Constant(value=value) + + return unaryop + + return eval_expr_unaryop(node) + + +class RewriteMasks(ast.NodeTransformer): + def __init__(self, masker: Masker, line_offsets: dict, python_version: PythonVersion): + self.masker = masker + self.line_offsets = line_offsets + self.python_version = python_version + + def visit_Name(self, node): + if node.id in self.masker.global_tab: + node.id = self.masker.mask(node.id) + + self.generic_visit(node) + return node + + def visit_Constant(self, node): + """Replace constants as well as calculate line_offsets introduced by replacing multiline strings""" + # update lno for multiline + if node.value in self.masker.global_tab: + if hasattr(node, "lineno") and hasattr(node, "end_lineno"): + if node.end_lineno > node.lineno: + self.line_offsets.update({node.lineno: node.end_lineno - node.lineno}) + + node.value = self.masker.mask(node.value) + self.generic_visit(node) + return node + + def visit_Global(self, node): + # drop unused globals; mask all others + node.names = [self.masker.mask(name) for name in node.names if name in self.masker.global_tab] + self.generic_visit(node) + return node + + def visit_Nonlocal(self, node): + node.names = [self.masker.mask(name) for name in node.names] + self.generic_visit(node) + return node + + def visit_BinOp(self, node): + node = evaluate_binop_optimizations(node, self.masker.version) + + if isinstance(node, ast.Constant): + node = self.visit_Constant(node) + elif isinstance(node, ast.Name): + node = self.visit_Name(node) + + self.generic_visit(node) + + # handle printf-style string formatting in 3.11 + if isinstance(node, ast.BinOp) and self.python_version >= (3, 11): + if isinstance(node.op, ast.Mod) and isinstance(node.left, ast.Constant) and isinstance(node.left.value, str) and type(node.right) in (ast.Tuple, ast.List): + format_specifier_re = r"(%[\#0\- \+]*(?:\*|\d+)?(?:.(?:\*|\d+))?[diouxXeEfFgGcrsa%])" + string_fragments = re.split(format_specifier_re, node.left.value) + + # resolve "%%" -> "%"; this isn't a real format specifier + for i in range(1, len(string_fragments) - 1, 2): + while string_fragments[i] == "%%": + string_fragments[i - 1] += "%" + string_fragments[i + 1] + string_fragments[i - 1 :] = string_fragments[i + 1 :] + # mask each fragment individually + if len(string_fragments) > 1: + for i, fragment in enumerate(string_fragments): + # don't try to mask the format specifier + if i % 2 == 1 or not fragment: + continue + string_fragments[i] = self.masker.mask(fragment) + node.left.value = "".join(string_fragments) + + return node + + def visit_UnaryOp(self, node): + # simplify negative numbers + node = evaluate_unaryop_optimizations(node, self.masker.version) + + if isinstance(node, ast.Constant): + node = self.visit_Constant(node) + elif isinstance(node, ast.Name): + node = self.visit_Name(node) + + self.generic_visit(node) + return node + + def visit_FunctionDef(self, node): + node.name = self.masker.mask(node.name) + + if node.returns is not None and ast.unparse(node.returns) in self.masker.global_tab: + node.returns = ast.Name(id=ast.unparse(node.returns)) + + self.generic_visit(node) + return node + + def visit_AsyncFunctionDef(self, node): + # mask return annotation + node.name = self.masker.mask(node.name) + + if node.returns is not None and ast.unparse(node.returns) in self.masker.global_tab: + node.returns = ast.Name(id=ast.unparse(node.returns)) + + self.generic_visit(node) + return node + + def visit_ClassDef(self, node): + node.name = self.masker.mask(node.name) + self.generic_visit(node) + return node + + def visit_Attribute(self, node): + if node.attr in self.masker.global_tab: + node.attr = self.masker.mask(node.attr) + elif ast.unparse(node) in self.masker.global_tab: + node = ast.Name(id=self.masker.mask(ast.unparse(node))) + self.generic_visit(node) + return node + + def visit_AnnAssign(self, node): + if self.masker.future_annotations: + if ast.unparse(node.annotation) in self.masker.global_tab: + node.annotation = ast.Name(id=self.masker.mask(ast.unparse(node.annotation))) + elif node.value is not None: + # if we can't mask the annotation, just remove the annotation + node = ast.Assign(targets=[node.target], value=node.value, lineno=node.lineno) + self.generic_visit(node) + else: + self.generic_visit(node) + if node.value is not None and not re.match(r'["\'(\[]*<', ast.unparse(node.annotation)): + # if we can't mask the annotation, just remove the annotation + node = ast.Assign(targets=[node.target], value=node.value, lineno=node.lineno) + return node + + def visit_ImportFrom(self, node): + if node.module is None: # edge case for "from .. import func" + node.module = self.masker.mask("") + else: + node.module = self.masker.mask(node.module) + node.module = str(self.masker.mask(node.level)) + str(node.module or "") + node.level = 0 + self.generic_visit(node) + return node + + def visit_arg(self, node): + node.arg = self.masker.mask(node.arg) + + if node.annotation and ast.unparse(node.annotation) in self.masker.global_tab: + node.annotation = ast.Name(id=ast.unparse(node.annotation)) + + self.generic_visit(node) + return node + + def visit_alias(self, node): + if node.name not in "*": + node.name = self.masker.mask(node.name) + if node.asname is not None: + node.asname = self.masker.global_tab.get(node.asname, node.asname) + self.generic_visit(node) + return node + + def visit_keyword(self, node): + if node.arg is not None: + node.arg = self.masker.mask(node.arg) + self.generic_visit(node) + return node + + def visit_ExceptHandler(self, node): + if getattr(node, "name", None) is not None: + node.name = self.masker.mask(node.name) + self.generic_visit(node) + return node + + def visit_If(self, node): + # don't try to mask trivially unreachable code + if isinstance(node.test, ast.Constant): + # we have to use deepcopy due to how generic_visit masks nodes, we cannot pass it the individual body. + if node.test.value is False: + # only mask else body + masked_node = copy.deepcopy(node) + self.generic_visit(masked_node) + node.orelse = copy.deepcopy(masked_node.orelse) + return node + if node.test.value is True: + # only mask if body + masked_node = copy.deepcopy(node) + self.generic_visit(masked_node) + node.body = copy.deepcopy(masked_node.body) + return node + + self.generic_visit(node) + return node + + +DUMMY_DECORATOR = "PYLINGUAL_DUMMY_DECORATOR_finj3igh309jhasfjn2oihg20ni3" + + +def add_dummy_decorators(source: str) -> str: + tree = ast.parse(source) + dummy_deco_transformer().generic_visit(tree) + return ast.unparse(tree) + + +class dummy_deco_transformer(ast.NodeTransformer): + def visit_ClassDef(self, node): + node.decorator_list.append(ast.Name(id=DUMMY_DECORATOR)) + self.generic_visit(node) + return node + + def visit_AsyncFunctionDef(self, node): + node.decorator_list.append(ast.Name(id=DUMMY_DECORATOR)) + self.generic_visit(node) + return node + + def visit_FunctionDef(self, node): + node.decorator_list.append(ast.Name(id=DUMMY_DECORATOR)) + self.generic_visit(node) + return node diff --git a/pylingual/masking/global_masker.py b/pylingual/masking/global_masker.py new file mode 100644 index 0000000..188d85a --- /dev/null +++ b/pylingual/masking/global_masker.py @@ -0,0 +1,264 @@ +from collections.abc import MutableMapping +from copy import deepcopy +from xdis import iscode +from xdis.cross_types import UnicodeForPython3, LongTypeForPython3 + +from pylingual.editable_bytecode import Inst +from pylingual.editable_bytecode.utils import comprehension_names, find_loadconst_codeobj_from_makefunc + + +# added type-sensitivity to the dict to differentiate true/1 and false/0 +class TypeSensitiveDict(MutableMapping): + def __init__(self, *args, **kwargs): + self.store = dict() + self.update(dict(*args, **kwargs)) # use the free update to set keys + + def __getitem__(self, key): + return self.store[self._key_transform(key)] + + def __setitem__(self, key, value): + self.store[self._key_transform(key)] = value + + def __delitem__(self, key): + del self.store[self._key_transform(key)] + + def __iter__(self): + return iter(self.store) + + def __len__(self): + return len(self.store) + + def __contains__(self, key): + return self._key_transform(key) in self.store + + def keys(self) -> list: + return [self._key_restore(key) for key in self.store.keys()] + + def items(self): + return ((self._key_restore(key), value) for key, value in self.store.items()) + + def values(self): + return self.store.values() + + def _key_transform(self, key): + if type(key) == UnicodeForPython3: + return (key.value.decode("utf-8"), str) + if type(key) == LongTypeForPython3: + return (key.value, int) + return (key, type(key)) + + def _key_restore(self, key): + return key[0] + + +#### Main Masker +class Masker: + blacklist = [ + "__doc__", + "__annotations__", + "__qualname__", + "__class__", + "return", # for return annotations + True, + False, + None, + ] + + def __init__(self, global_table: TypeSensitiveDict | None = None): + self.global_tab = global_table if global_table is not None else TypeSensitiveDict() + + def mask(self, tok): + """Mask a token, must be in the global_table.""" + return self.global_tab[tok] if not any(tok == t and type(tok) == type(t) for t in self.blacklist) else tok + + def unmask(self, value): + """Unmask a token, value must be a metatoken value in the global_table; or this function will fail loudly""" + key = list(self.global_tab.keys())[list(self.global_tab.values()).index(value)] + return key + + def parse_MAKE_FUNCTION_info(self, inst: Inst) -> str: + """Parses out information about MAKE_FUNCTION, like args and flags + Used in get_model_view()""" + if inst.opcode != inst.bytecode.opcode.MAKE_FUNCTION: + raise ValueError("Inst is not type MAKE_FUNCTION") + + target_inst = find_loadconst_codeobj_from_makefunc(inst) + if target_inst is None: + raise ValueError("Could not find target LOAD_CONST codeobj from MAKE_FUNCTION") + target_co = target_inst.argval + + func_info = [] # list of info fields to use as argval for MAKE_FUNCTION + + flags_make_func = int(inst.argval) + if bool(flags_make_func & 0b0001): # b_default_vals + func_info.append("defaults") + if bool(flags_make_func & 0b0010): # b_kwonly_defaults + func_info.append("kwonly_defaults") + if bool(flags_make_func & 0b0100): # b_param_annotations + if self.future_annotations: + func_info.append("annotations-FUTURE") + else: + func_info.append("annotations") + if bool(flags_make_func & 0b1000): # b_free_vars + func_info.append("closures") + + # flags from the target code object + flags_co = int(target_co.co_flags) + if bool(flags_co & 0b10000000): # coroutine + func_info.append("coroutine") + if bool(flags_co & 0b1000000000): # async_generator + func_info.append("async_generator") + + posargcount = target_co.co_argcount + if not hasattr(target_co, "co_posonlyargcount"): + setattr(target_co, "co_posonlyargcount", 0) + argcount = posargcount + target_co.co_kwonlyargcount + + # parse args + if argcount: + args = [self.mask(inst.bytecode.resolve_namespace(arg)) for arg in target_co.co_varnames[:argcount]] + end = 0 + if posargcount: + begin, end = 0, posargcount + func_info.append(f"args: [{', '.join(args[begin:end])}]") + if target_co.co_posonlyargcount: + begin, end = end, end + target_co.co_posonlyargcount + func_info.append(f"posonly: [{', '.join(args[begin:end])}]") + if target_co.co_kwonlyargcount: + begin, end = end, end + target_co.co_kwonlyargcount + func_info.append(f"kwonly: [{', '.join(args[begin:end])}]") + + # kwargs and varargs + has_kwargs = bool(flags_co & 0b0100) + if has_kwargs: + func_info.append(f"kwarg: [*{self.mask(inst.bytecode.resolve_namespace(target_co.co_varnames[argcount]))}]") + if bool(flags_co & 0b1000): + func_info.append(f"vararg: [**{self.mask(inst.bytecode.resolve_namespace(target_co.co_varnames[argcount + int(has_kwargs)]))}]") + + return ", ".join(func_info) + + def get_model_view(self, inst: Inst) -> str: + """Get a dissassembled view of the instruction as the model will view it + - Replaces literals, varnames, consts etc with a mask defined in the global_table arg. + Will recursively replace items in list-like objects as well + - Brings necessary information for function definitions up from the child code obj + - Simplifies jump args + - Removes Offset notation except for jump_targets + + :param inst: Instruction we are trying to view + + """ + view = "" + + if inst.opcode == inst.bytecode.opcode.MAKE_FUNCTION: + # bring up necessary information from child bytecode for function def creation + view = f"{inst.opname} , {inst.arg}" + # parse for load const with our target bytecode + func_info = self.parse_MAKE_FUNCTION_info(inst) + if func_info: + view += f" ({func_info})" + + elif inst.optype in ("nargs", "vargs", "compare"): + view = f"{inst.opname} , {inst.argrepr if inst.argrepr else inst.argval}" + + elif inst.optype == "name": + view = f"{inst.opname} , {self.mask(inst.bytecode.resolve_namespace(inst.argval))}" + # additional import context to differ some import syntax + if inst.opname == "IMPORT_NAME" and "." in inst.argval: + view += "-DOT" + elif inst.opname == "LOAD_NAME" and inst.argval == "__annotations__" and self.future_annotations: + view += "-FUTURE" + + elif inst.optype in ("const", "local"): + if iscode(inst.argval): + if inst.argval.co_name in comprehension_names: + view = f"{inst.opname} , " + else: + view = f"{inst.opname} , " + + elif isinstance(inst.argval, str) and inst.argval in self.global_tab: # have to do this check incase string is varname of type annotation + view = f"{inst.opname} , {repr(self.mask(inst.argval))}" + + elif type(inst.argval) in (list, tuple, frozenset, set): + # do recursive in-place replacement of list elems if they are strs or bytestrs + + def replace_list(consts): + """recursive replacement of elements in arbitrary list-like objects""" + for idx, const in enumerate(consts): + if isinstance(const, str): + consts[idx] = repr(self.mask(inst.bytecode.resolve_namespace(const))) + elif const is None: + continue # don't mask None + elif type(const) in (list, tuple, frozenset): + consts[idx] = type(const)(replace_list(list(const))) + else: + consts[idx] = self.mask(const) + return consts + + consts = list(deepcopy(inst.argval)) + consts = replace_list(consts) + + if inst.bytecode.version < (3, 11): + # Format keyword argument list + # We left pad the list of kwargs so the model doesnt have to "look ahead" + next_insts = inst.next_instructions + next_inst = next_insts[0] if next_insts != [] else None + if next_inst is not None and inst.opname == "LOAD_CONST" and next_inst.opname == "CALL_FUNCTION_KW": + consts = [""] * (next_inst.argval - len(consts)) + consts + + # cast back to original type and print repr + # demote quotes one layer + arg_repr = repr(type(inst.argval)(consts)).replace("'", "").replace('"', "'") + view = f"{inst.opname} , {arg_repr}" + else: + view = f"{inst.opname} , {self.mask(inst.bytecode.resolve_namespace(inst.argval))}" + + # inst has other arg type, format + elif inst.has_arg: + # check for jump to use simplified jump format + if inst.is_jump: + jump_direction_indicator = "v~>" if inst.target.offset > inst.offset else "^~>" + view = f"{inst.opname} {inst.argrepr} {jump_direction_indicator}" + elif inst.optype is None or inst.optype == "??" or inst.optype == "encoded_arg": + # don't mask IS_OP args + view = f"{inst.opname} , {inst.argrepr if inst.argrepr else inst.argval}" + else: + if inst.argval in self.global_tab: + view = f"{inst.opname} , {self.mask(inst.argval)}" + else: + view = f"{inst.opname} , {inst.argrepr}" + # inst sets up annotations and __future__ annotations imported + elif inst.opname == "SETUP_ANNOTATIONS" and self.future_annotations: + view = "SETUP_ANNOTATIONS-FUTURE" + # no arg case + else: + view = f"{inst.opname}" + + # add offset if inst is a jump target + if inst.is_jump_target: + view = f"{inst.offset} {view}" + # create list of offsets greater than or less than inst, for all jump origins to this inst + jumps_greater_or_less = [inst.offset < inst.offset for inst in inst.jumped_to_from_insts] + if any(jumps_greater_or_less): # 1 in list + view = f"^~> {view}" + if not all(jumps_greater_or_less): # 0 in list + view = f"v~> {view}" + + # Add exception table information + if inst.bytecode.named_exception_table is not None: + if inst.exception_target: + view = f"E-> {view}" + if inst.exception_end: + view = f"{view} E-END" + + # check if next instruction is the start of entry in exception_table + next_inst = None + for inst in inst.next_instructions: + if inst.opname not in ("CACHE", "PRECALL"): + next_inst = inst + break + + if next_inst is not None and next_inst.exception_start: + view = f"{view} E-> {next_inst.exception_start}" + + return view diff --git a/pylingual/masking/model_disasm.py b/pylingual/masking/model_disasm.py new file mode 100644 index 0000000..5e74360 --- /dev/null +++ b/pylingual/masking/model_disasm.py @@ -0,0 +1,162 @@ +from __future__ import annotations + +import ast +import pathlib +import re +from copy import deepcopy +from typing import TYPE_CHECKING + +from pylingual.utils.use_escape_sequences import use_escape_sequences +from pylingual.utils.version import PythonVersion + +if TYPE_CHECKING: + from pylingual.editable_bytecode import EditableBytecode + +from pylingual.masking.ast_masker import RewriteMasks, custom_unparse +from pylingual.masking.global_masker import Masker + +mask_regex = re.compile(r"(?<=)") + + +def create_global_masker(bytecode: EditableBytecode) -> Masker: + """Creates four flat global tables of names, locals, consts, and freevars in given Bytecode for use with improved model view + Global Tables map local code obj tables: name, varname, const, and freevar to the global table + - For additional model context, values that match across the name and const tab will have their matching + partner in the other table appended to their token. ex: = + Access table in global dict via keywork of table optype""" + + global_masker = Masker() + global_tab = global_masker.global_tab + global_idx = 0 + + future_flag = 0x1000000 if bytecode.version > (3, 7) else 0x100000 + global_masker.future_annotations = bool(bytecode.codeobj.co_flags & future_flag) + global_masker.version = bytecode.version + + for bc in bytecode.iter_bytecodes(): + bc_co = bc.to_code(no_lnotab=True) + + # create consts + consts = list(deepcopy(bc_co.co_consts)) + while consts: + const = consts.pop(0) + # Don't mask None + if const is None: + continue + if type(const) in (list, tuple, frozenset, set): + consts.extend(const) + else: + global_tab.update({bc.resolve_namespace(const): f""}) + global_idx += 1 + + # create names + for name in bc_co.co_names: + if name in global_tab: + continue + global_tab.update({bc.resolve_namespace(name): f""}) + global_idx += 1 + + for free in bc_co.co_freevars: + if free in global_tab: + continue + global_tab.update({free: f""}) + global_idx += 1 + + for local in bc_co.co_varnames: + if local in global_tab: + continue + global_tab.update({bc.resolve_namespace(local): f""}) + global_idx += 1 + + global_tab.update({bc_co.co_name: f""}) + global_idx += 1 + + return global_masker + + +def mask_source(file_path: pathlib.Path, masker: Masker, python_version: PythonVersion) -> str: + """Replace source strings with tokens from keys in global_tab, + masks source and provides offsets incured by multiline string replacements""" + text = file_path.read_text() + tree = ast.parse(text, feature_version=python_version.as_tuple()) + + line_offsets = dict() + tree = RewriteMasks(masker, line_offsets, python_version).generic_visit(tree) + + source_text = custom_unparse(tree, masker) + source_lines = source_text.splitlines() + + re_added_lines = 0 + for line_offset, lines_to_add in line_offsets.items(): + insertion_target = line_offset + re_added_lines + source_lines[insertion_target:insertion_target] = [""] * lines_to_add + + return "\n".join(source_lines) + + +def restore_masked_source(file_path: pathlib.Path, masker: Masker, python_version: PythonVersion) -> str: + """Creates a large regex of all the tokens and their respective values + Replaces everything in file text in one pass.""" + return restore_masked_source_text(file_path.read_text(), masker, python_version) + + +def format_source_replacement(mask_value: str) -> str: + if mask_value is ...: + return "..." + if type(mask_value) in (int, float) and mask_value < 0: + return f"({mask_value})" + if type(mask_value) != str: + return str(mask_value) + + formatted_mask_value = use_escape_sequences(mask_value) + + return formatted_mask_value + + +def fix_jump_targets(disasm: str) -> str: + jump_target_re = r"to (\d+) ([\^v]~>)" + jump_target_map = {target: f"TARGET_{ind}" for ind, target in enumerate(sorted(set(match.group(1) for match in re.finditer(jump_target_re, disasm))))} + + # remove external jump entry points + incoming_jump_re = r"([\^v]~>) (\d+) " + result = re.sub(incoming_jump_re, lambda match: f"{match.group(1)} {jump_target_map[match.group(2)]} " if match.group(2) in jump_target_map else f"{match.group(1)} ", disasm) + + # only enforce order on external jumps + result = re.sub(jump_target_re, lambda match: f"to {jump_target_map[match.group(1)]} {match.group(2)}", result) + return result + + +def restore_masked_source_text(text: str, masker: Masker, python_version: PythonVersion) -> str: + """Creates a large regex of all the tokens and their respective values + Replaces everything in file text in one pass.""" + replacements = {re.escape(v): format_source_replacement(k) for k, v in masker.global_tab.items()} # we use encode + decode so multiline strings get replaced correctly + re_pattern = re.compile("|".join(replacements.keys())) + result = re_pattern.sub(lambda match: replacements[match.group()], text) + + # replace imports with a module starting with a number, with that number amount of dots for relative imports + re_rel_pattern = r"^(\s*)(import|from)\s*(\d+)(.*)" + result_rel_imports = re.sub(re_rel_pattern, lambda match: f"{match.group(1)}{match.group(2)} {'.' * int(match.group(3))}{match.group(4)}", result, 0, re.MULTILINE) + + # normalize with parse+unparse to catch replacement errors and simplify whitespace + try: + return ast.unparse(ast.parse(result_rel_imports, feature_version=python_version.as_tuple())) + except (SyntaxError, IndentationError): + return result_rel_imports + + +# replace mask values to start at 0 and count up +def normalize_masks(statement: str) -> tuple[str, list[str]]: + masks = mask_regex.findall(statement) + mask_order = [x for i, x in enumerate(masks) if masks.index(x) == i] + return mask_regex.sub(lambda x: str(mask_order.index(x.group(0))), statement), mask_order + + +# reverts masks to their original value +def restore_masks(translation: str, mask_order: list[str]): + def restore_mask(match): + i = int(match.group(0)) + # sometimes model adds random masks that did not appear in bytecode, + # so we have to check if mask is in bounds + return mask_order[i] if i < len(mask_order) else match.group(0) + + return mask_regex.sub(restore_mask, translation) diff --git a/pylingual/models.py b/pylingual/models.py new file mode 100644 index 0000000..ada1e21 --- /dev/null +++ b/pylingual/models.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import yaml +import logging + +from collections import OrderedDict +from pathlib import Path +from typing import TYPE_CHECKING + +from pylingual.masking.model_disasm import fix_jump_targets, normalize_masks, restore_masks +from pylingual.utils.lists import flatten +from pylingual.utils.tracked_list import TrackedDataset, TRANSLATION_STEP +from pylingual.utils.version import PythonVersion +from pylingual.utils.lazy import lazy_import + +if TYPE_CHECKING: + import torch + import transformers + import huggingface_hub +else: + lazy_import("torch") + lazy_import("transformers") + lazy_import("huggingface_hub") + +logger = logging.getLogger(__name__) + + +# translator with caching +class CacheTranslator: + """ + Adds cache support for statement translation + + :param translator : The loaded translation model + :param maxsize : The maximum amount of cached items + """ + + def __init__(self, translator: transformers.TranslationPipeline, maxsize=50000): + self.translator = translator + self.cache = OrderedDict() + self.maxsize = maxsize + + def __getitem__(self, item): + self.cache.move_to_end(item) + return self.cache[item] + + def _translate_and_decode(self, translation_requests: TrackedDataset | list[str], batch_size: int = 32, **kwargs) -> list[str]: + # return_tensors=True prevents standard postprocessing which skips special tokens + translation_result = self.translator(translation_requests, return_tensors=True, batch_size=batch_size, **kwargs) + decoded_results = [] + for result in flatten(translation_result): + # explicitly filter out the special tokens we want to skip: , , , , + filtered_tokens = [tok for tok in result["translation_token_ids"].tolist() if tok not in [0, 1, 2, 3, 4]] + # decode the remaining tokens + decoded_results.append(self.translator.tokenizer.decode(filtered_tokens, skip_special_tokens=False)) + + return decoded_results + + def _translate_with_backoff(self, translation_requests: TrackedDataset) -> list[str]: + try: + return self._translate_and_decode(translation_requests, batch_size=32) + except Exception as e: + logger.info(f"Lowering translation batch size ({e})") + # Try with batch_size = 1 if normal translation fails: + try: + return self._translate_and_decode(translation_requests, batch_size=1) + except Exception as e: + logger.info(f"Lowering translation batch size ({e})") + translation_results = [] + # try one-by-one if batch_size=1 fails + for request in translation_requests: + try: + translation_results.append(self._translate_and_decode([request], batch_size=1)[0]) + except Exception: + # last resort fallback + translation_results.append("'''Decompiler error: line too long for translation. Please decompile this statement manually.'''") + return translation_results + + def __call__(self, args: list, **_): + normalized_args = [normalize_masks(fix_jump_targets(x)) for x in args] + + # New are those not in the local cache + new = TrackedDataset(TRANSLATION_STEP, list({norm for norm, _ in normalized_args if norm not in self.cache})) + + # Now, "new" has been updated to those not in local + for arg, result in zip(new.x, self._translate_with_backoff(new)): + self.cache[arg] = result + + results = [restore_masks(self[norm], order) for norm, order in normalized_args] + while len(self.cache) > self.maxsize: + self.cache.popitem(last=False) + return results + + +def load_models(config_file: Path = Path("pylingual/decompiler_config.yaml"), version: PythonVersion = PythonVersion(3.9), token=False) -> tuple[transformers.Pipeline, CacheTranslator]: + logger.info(f"Loading models for {version}...") + with config_file.open() as f: + config = yaml.safe_load(f) + seg_config = config[f"v{version}"]["SEGMENTATION_MODEL"] + stmt_config = config[f"v{version}"]["STATEMENT_MODEL"] + + ################################# + # Segmentation model components # + ################################# + segmentation_model = transformers.AutoModelForTokenClassification.from_pretrained( + pretrained_model_name_or_path=seg_config["REPO"], + revision=seg_config["REVISION"], + token=token, + ) + segmentation_tokenizer_file = huggingface_hub.hf_hub_download(repo_id=seg_config["TOKENIZER"], filename="tokenizer.json", token=token) + segmentation_tokenizer = transformers.PreTrainedTokenizerFast( + tokenizer_file=segmentation_tokenizer_file, + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", + ) + if torch.cuda.is_available(): + device = torch.device("cuda:0") + else: + logger.warning("Using CPU for models") + device = torch.device("cpu") + segmenter = transformers.pipeline("token-classification", model=segmentation_model, tokenizer=segmentation_tokenizer, aggregation_strategy="none", device=device) + ######################################### + # Sequence translation model components # + ######################################### + translation_model = transformers.T5ForConditionalGeneration.from_pretrained(stmt_config["REPO"], revision=stmt_config["REVISION"], token=token) + translation_tokenizer = transformers.RobertaTokenizer.from_pretrained(stmt_config["TOKENIZER"], token=token) + translator = transformers.TranslationPipeline(model=translation_model, tokenizer=translation_tokenizer, max_length=512, truncation=False, device=device) + + return segmenter, CacheTranslator(translator) diff --git a/pylingual/segmentation/segmentation_search_strategies.py b/pylingual/segmentation/segmentation_search_strategies.py new file mode 100644 index 0000000..93b7b70 --- /dev/null +++ b/pylingual/segmentation/segmentation_search_strategies.py @@ -0,0 +1,140 @@ +from __future__ import annotations +import heapq +import itertools + +from typing import TYPE_CHECKING, List, Tuple, Dict, Callable, Generator, Iterable +from pylingual.utils.lazy import lazy_import + +if TYPE_CHECKING: + import numpy as np + + BitMap = List[bool] + SearchStrategy = Callable[[List[float]], List[BitMap]] + PriorityFunction = Callable[[List[float]], np.array] +else: + lazy_import("numpy", "np") + +############# +# UTILITIES # +############# + + +def filter_subwords(results): + # Get results that are not associated with a subword + return [result for result in results if not result["word"].startswith("##")] + + +# incrementally generates the index-representation of bitstrings of length length and with num_ones ones +def bitstring_index_generator(length: int, num_ones: int) -> Generator[Tuple[int], None, None]: + if num_ones > length: + return + + if num_ones == 0: + yield tuple() + return + + # start by having ones in all the smallest positions + bit_indices = list(range(num_ones)) + yield tuple(bit_indices) + while bit_indices[0] < length - num_ones: + # increment the least significant bit and handle cascading effects + bit_indices[0] += 1 + for i in range(num_ones - 1): + # when you overlap with the next bit, increase it and set yourself back + if bit_indices[i] == bit_indices[i + 1]: + bit_indices[i + 1] += 1 + bit_indices[i] = bit_indices[i - 1] + 1 if i > 0 else 0 + yield tuple(bit_indices) + + +# convert bitstring indices to numeric representation +def indices_to_num(indices: List[int]) -> int: + return sum(2 ** int(digit) for digit in indices) + + +# https://docs.python.org/3/library/itertools.html#itertools.pairwise not introduced until 3.10 +# sliding_window('ABCDEFG') --> AB BC CD DE EF FG +def sliding_window(iterable: Iterable, window=2) -> Iterable: + if window < 1: + raise ValueError("Window size must be at least 1") + + iterators = [iterable] + + for _ in range(window - 1): + iterators[-1], next_window_member = itertools.tee(iterators[-1]) + next(next_window_member, None) + iterators.append(next_window_member) + + return zip(*iterators) + + +def bitmap_to_entities(bitmap: BitMap) -> List[str]: + # map of (current, next) pairs in the bitstring to segmentation entities + entity_map = { + (True, True): "B", + (True, False): "B", + (False, True): "E", + (False, False): "I", + } + + entities = [entity_map[pair] for pair in sliding_window(bitmap)] + entities += "B" if bitmap[-1] else "E" + return entities + + +def entities_to_bitmap(entities) -> BitMap: + return np.array([entity == "B" for entity in entities], dtype=bool) + + +######################## +# STRATEGY DEFINITIONS # +######################## + + +# a general m_deep_top_k implementation that takes the priority function as a parameter +def m_deep_top_k(scores: List[float], m: int, k: int, priority_function: PriorityFunction) -> List[BitMap]: + # flip_candidate_lists[i] is an iterator over all length l bitstrings with i ones; each list is sorted numerically + length = len(scores) + flip_candidate_lists = [bitstring_index_generator(length, num_flips) for num_flips in range(m + 1)] + + # generate a sorted list of the flip candidates (lazily evaluated) and collect the top k elements + sorted_flip_candidates = heapq.merge(*flip_candidate_lists, key=indices_to_num) + top_k_candidates = list(itertools.islice(sorted_flip_candidates, k)) + + # convert the feature-space indices into problem-space indices + # the priority map is a list of indices in order of their estimated uncertainty + priority_map = priority_function(scores) + flip_sets = [priority_map[list(candidate_flips)] for candidate_flips in top_k_candidates] + + # to match the formalization, build the error strings + error_strings = [] + for flip_set in flip_sets: + error_string = np.full(length, False) + error_string[flip_set] = True + error_strings.append(list(error_string)) + + return error_strings + + +def get_top_k_predictions(strategy: SearchStrategy, predicted_boundary: List[Dict]) -> List[List[str]]: + entities = [prediction["entity"] for prediction in predicted_boundary] + initial_segmentation = entities_to_bitmap(entities) + + scores = [prediction["score"] for prediction in predicted_boundary] + transformations = strategy(scores) + + candidate_segmentations = [np.logical_xor(initial_segmentation, transformation) for transformation in transformations] + + # convert to entities for compatibility with the pipeline + top_k_boundaries = [bitmap_to_entities(segmentation) for segmentation in candidate_segmentations] + return top_k_boundaries + + +###################### +# PRIORITY FUNCTIONS # +###################### + + +# this strategy simply flips predictions in order of least confidence +def naive_confidence_priority(scores: List[float]) -> np.array: + return np.argsort(scores) diff --git a/pylingual/segmentation/sliding_window.py b/pylingual/segmentation/sliding_window.py new file mode 100644 index 0000000..e3bc3c3 --- /dev/null +++ b/pylingual/segmentation/sliding_window.py @@ -0,0 +1,140 @@ +import itertools + + +# make windows based on the token sizes instrutions will be an instruction along with it's token size +def sliding_window(instructions: list, max_window_size: int, step_size: int = 1) -> tuple[list[str], list[int]]: + # go through each instruction in a code object and fill up windows + instructions_iter = enumerate(instructions) + + try: + while True: + # start a new window + instructions_iter, window_source = itertools.tee(instructions_iter) + + # take elements until it would overflow the window + window = [] + index_window = [] + window_len = 0 + + # this loop will raise stopIteration when we run out of instructions + while window_item := next(window_source): + inst_index, (inst, inst_len) = window_item + + # end the window if we would exceed the max_window_size + if inst_len + window_len > max_window_size: + break + + window.append(inst) + index_window.append(inst_index) + window_len += inst_len + + yield (window, index_window) + + # step forward step_size tokens + tokens_stepped = 0 + + while step_item := next(instructions_iter): + inst_index, (inst, inst_len) = step_item + tokens_stepped += inst_len + if tokens_stepped >= step_size: + break + except StopIteration: + # yield the final window (partially full) + yield (window, index_window) + + +# merges the bytecode together based on a point system given the division value for be results use an odd number and given the steps used in the sliding window +def merge(window_coords: list[tuple], window_segmentation_results: list[list[dict]], inst_index: list, window_size: int, step: int) -> list[list[dict]]: + # for each codeobject align and score + sorted_windows = align_segmentation_window_results(window_coords, window_segmentation_results, inst_index) + + weighted_segmentation_results = confidence_based_score(sorted_windows) + + # get ready for the merging + + entity_list = [] + for codeobj in weighted_segmentation_results: + codeobj_entities = [] + + for inst in codeobj: + highest = max(inst[1], key=inst[1].get) + final_choice = inst[0] + + if highest == "B_score": + final_choice["entity"] = "B" + elif highest == "I_score": + final_choice["entity"] = "I" + elif highest == "E_score": + final_choice["entity"] = "E" + codeobj_entities.append(final_choice) + + entity_list.append(codeobj_entities) + + return entity_list + + +# align windows to make scoring easier +def align_segmentation_window_results( + window_coords: list[tuple], + window_segmentation_results: list[list[dict]], + inst_index: list, +) -> dict[int, list[list[dict]]]: + # returns a dict of code object index -> list of instruction results, where each instruction result is a list of segmentation results for that instruction + segmentation_result_dict = dict() + for (codeobj_index, _), window_results, window_inst_indices in zip(window_coords, window_segmentation_results, inst_index): + if codeobj_index not in segmentation_result_dict: + segmentation_result_dict[codeobj_index] = dict() + + codeobj_result_dict = segmentation_result_dict[codeobj_index] + for inst_result, inst_index in zip(window_results, window_inst_indices): + if inst_index not in codeobj_result_dict: + codeobj_result_dict[inst_index] = list() + + codeobj_result_dict[inst_index].append(inst_result) + + segmentation_result_dict[codeobj_index] = codeobj_result_dict + + # bake codeobj_result dicts into lists + baked_result_dict = dict() + for codeobj_index, codeobj_result_dict in segmentation_result_dict.items(): + if bool(codeobj_result_dict): + codeobj_length = max(codeobj_result_dict.keys()) + 1 + codeobj_result_list = [None] * codeobj_length + + for instruction_index, instruction_result in codeobj_result_dict.items(): + codeobj_result_list[instruction_index] = instruction_result + + # make sure we didn't miss any indices! + assert None not in codeobj_result_list + + baked_result_dict[codeobj_index] = codeobj_result_list + + return baked_result_dict + + +# given a list of sorted windows for each codeobject we will score b,i,e for each instruction based on the segmentation models confidence level +def confidence_based_score(aligned_segmentation_results: list[list[dict]]) -> list[list[dict]]: + codeobj_scores = [] + for codeobj in range(len(aligned_segmentation_results)): + inst_list = [] + + for inst in aligned_segmentation_results[codeobj]: + scores = {"B_score": 0, "I_score": 0, "E_score": 0} + + # go through each result from the windows and give a score to b,i,e based on their offsets + for item in inst: + confidence_score = item["score"] + if item["entity"] == "B": + scores["B_score"] += confidence_score + elif item["entity"] == "I": + scores["I_score"] += confidence_score + elif item["entity"] == "E": + scores["E_score"] += confidence_score + + # scores for instructions + inst_list.append((inst[0], scores)) + + # scores for the instructions in each code object + codeobj_scores.append(inst_list) + + return codeobj_scores diff --git a/pylingual/utils/ascii_art.py b/pylingual/utils/ascii_art.py new file mode 100644 index 0000000..ac8292c --- /dev/null +++ b/pylingual/utils/ascii_art.py @@ -0,0 +1,25 @@ +import importlib.metadata + +PYLINGUAL_ART = r''' + ,ggggggggggg, ,gggg, +dP"""88""""""Y8, d8" "8I ,dPYb, +Yb, 88 `8b 88 ,dP IP'`Yb + `" 88 ,8P 8888888P" gg I8 8I + 88aaaad8P" 88 "" I8 8' + 88"""""gg gg 88 gg ,ggg,,ggg, ,gggg,gg gg gg ,gggg,gg I8 dP + 88 I8 8I ,aa,_88 88 ,8" "8P" "8, dP" "Y8I I8 8I dP" "Y8I I8dP + 88 I8, ,8IdP" "88P 88 I8 8I 8I i8' ,8I I8, ,8I i8' ,8I I8P + 88 ,d8b, ,d8IYb,_,d88b,,_ _,88,_,dP 8I Yb,,d8, ,d8I ,d8b, ,d8b,,d8, ,d8b,,d8b,_ + 88 P""Y88P"888"Y8P" "Y888888P""Y88P' 8I `Y8P"Y8888P"8888P'"Y88P"`Y8P"Y8888P"`Y88P'"Y88 + ,d8I' ,d8I' + ,dP'8I ,dP'8I + ,8" 8I ,8" 8I + I8 8I I8 8I + `8, ,8I `8, ,8I + `Y8P" `Y8P" +'''.strip("\n") + +PYLINGUAL_SUBHEADER = f""" +The University of Texas at Dallas, Syssec Lab +{importlib.metadata.version("pylingual")} - https://pylingual.io +""".rstrip() diff --git a/pylingual/utils/generate_bytecode.py b/pylingual/utils/generate_bytecode.py new file mode 100644 index 0000000..18585b9 --- /dev/null +++ b/pylingual/utils/generate_bytecode.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +import subprocess +import sys +import shlex +import py_compile + +from pylingual.utils.version import PythonVersion + + +class CompileError(Exception): + success = False + + +def compile_version(py_file, out_file, version): + py_file = str(py_file) + out_file = str(out_file) + version = PythonVersion(version) + if version == sys.version_info: + try: + py_compile.compile(py_file, cfile=out_file, doraise=True, optimize=0) + except py_compile.PyCompileError as e: + raise CompileError(str(e)) + return + compile_cmd = f"import py_compile, sys; assert sys.version_info[:2] == {version.as_tuple()!r}; py_compile.compile({py_file!r}, cfile={out_file!r})" + cmd = f"env PYENV_VERSION={version.as_str()} PYTHONWARNINGS=ignore pyenv exec python -c {shlex.quote(compile_cmd)}" + output = subprocess.run(cmd, shell=True, capture_output=True, text=True) + if output.stderr: + raise CompileError(output.stderr) diff --git a/pylingual/utils/get_logger.py b/pylingual/utils/get_logger.py new file mode 100644 index 0000000..f2d18f6 --- /dev/null +++ b/pylingual/utils/get_logger.py @@ -0,0 +1,30 @@ +import datetime +import logging +import os + + +def get_logger(name: str) -> logging.Logger: + # Create a logger + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + # Create a file handler + current_datetime = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + if not os.path.exists("logs"): # create a logging directory if not exists + os.makedirs("logs") + file_handler = logging.FileHandler(f"logs/{name}_{current_datetime}.log", mode="w") + file_handler.setLevel(logging.DEBUG) + + # Create a console handler + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.INFO) + + # Create a formatter and add it to the handlers + formatter = logging.Formatter("%(asctime)s- %(levelname)s - %(message)s") + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + + # Add the handlers to the logger + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + return logger diff --git a/pylingual/utils/lazy.py b/pylingual/utils/lazy.py new file mode 100644 index 0000000..8eecab3 --- /dev/null +++ b/pylingual/utils/lazy.py @@ -0,0 +1,17 @@ +import inspect + + +class lazy_import: + """ + Delay importing a module until it is used + """ + + def __init__(self, name: str, as_name: str | None = None): + self.globals = inspect.currentframe().f_back.f_globals + self.as_name = as_name or name + self.globals[self.as_name] = self + self.name = name + + def __getattr__(self, attr): + self.globals[self.as_name] = module = __import__(self.name) + return getattr(module, attr) diff --git a/pylingual/utils/lists.py b/pylingual/utils/lists.py new file mode 100644 index 0000000..71313a4 --- /dev/null +++ b/pylingual/utils/lists.py @@ -0,0 +1,11 @@ +def unflatten(flattened: list, reference: list[list]): + for i, request in enumerate(reference): + flattened[i : i + len(request)] = [flattened[i : i + len(request)]] + + +def flatten(x): + for e in x: + if isinstance(e, list): + yield from flatten(e) + else: + yield e diff --git a/pylingual/utils/tracked_list.py b/pylingual/utils/tracked_list.py new file mode 100644 index 0000000..7acb2d0 --- /dev/null +++ b/pylingual/utils/tracked_list.py @@ -0,0 +1,63 @@ +from typing import TYPE_CHECKING +from .lazy import lazy_import + +if TYPE_CHECKING: + import transformers +else: + lazy_import("transformers") + +SEGMENTATION_STEP = "Segmentation" +TRANSLATION_STEP = "Translation" +CFLOW_STEP = "Control Flow" +CORRECTION_STEP = "Error Correction" + + +class TrackedList: + """ + List-like class that calls self.progress on each element access + Used to display progress bars when PyLingual is run as a script, does nothing otherwise + """ + + def __init__(self, name: str, x: list): + self.name = name + self.x = x + self.i = 0 + self.init() + + # overwritten when run as script + def init(self): + pass + + def __getitem__(self, i): + self.progress(i - self.i) + self.i = i + return self.x[i] + + def __len__(self): + return len(self.x) + + def __iter__(self): + return self + + def __next__(self): + try: + n = self.x[self.i] + except: + raise StopIteration() + self.progress(1) + self.i += 1 + return n + + # overwritten when run as script + def progress(self, i: int): + pass + + +class TrackedDataset(TrackedList): + """ + Like TrackedList, but inherits from Dataset + """ + + def __init__(self, name: str, x: list): + super().__init__(name, x) + TrackedDataset.__bases__ = (TrackedList, transformers.pipelines.base.Dataset) diff --git a/pylingual/utils/use_escape_sequences.py b/pylingual/utils/use_escape_sequences.py new file mode 100644 index 0000000..edd8a7f --- /dev/null +++ b/pylingual/utils/use_escape_sequences.py @@ -0,0 +1,17 @@ +def use_escape_sequences(s): + escapes = { + "\\": "\\\\", + "'": "\\'", + '"': '\\"', + "\a": "\\a", + "\b": "\\b", + "\f": "\\f", + "\n": "\\n", + "\r": "\\r", + "\t": "\\t", + "\v": "\\v", + "\x00": "\\x00", + } + for a, b in escapes.items(): + s = s.replace(a, b) + return s diff --git a/pylingual/utils/version.py b/pylingual/utils/version.py new file mode 100644 index 0000000..20fc4f1 --- /dev/null +++ b/pylingual/utils/version.py @@ -0,0 +1,75 @@ +supported_tuples = [(3, x) for x in range(6, 14)] +version_str = {f"{x[0]}{x[1]}": x for x in supported_tuples} | {f"{x[0]}.{x[1]}": x for x in supported_tuples} + + +class PythonVersion: + major: int + minor: int + _t: tuple + + @staticmethod + def normalize(x) -> tuple[int, int] | None: + if isinstance(x, PythonVersion): + return x._t + if isinstance(x, float): + if x == 3.1: + x = "3.10" + x = str(x) + if isinstance(x, str): + x = version_str.get(".".join(map(str.strip, x.split(".")[:2]))) + if isinstance(x, int): + x = (3, x) + if isinstance(x, tuple): + if len(x) >= 2 and isinstance(x[0], int) and isinstance(x[1], int): + return x[:2] + + def is_supported(self) -> bool: + return self.as_tuple() in supported_tuples + + def __init__(self, x): + v = PythonVersion.normalize(x) + if v is None: + raise ValueError(f"version {x} is invalid") + self.major, self.minor = v + self._t = v + + def __str__(self): + return f"{self.major}.{self.minor}" + + def __eq__(self, o): + return PythonVersion.normalize(o) == self._t + + def __ne__(self, o): + norm = PythonVersion.normalize(o) + return norm is not None and self._t != norm + + def __ge__(self, o): + norm = PythonVersion.normalize(o) + return norm is not None and self._t >= norm + + def __le__(self, o): + norm = PythonVersion.normalize(o) + return norm is not None and self._t <= norm + + def __gt__(self, o): + norm = PythonVersion.normalize(o) + return norm is not None and self._t > norm + + def __lt__(self, o): + norm = PythonVersion.normalize(o) + return norm is not None and self._t < norm + + def __getitem__(self, i): + return self._t[i] + + def as_str(self): + return str(self) + + def as_float(self): + return float(str(self)) + + def as_tuple(self): + return self._t + + +supported_versions = list(map(PythonVersion, supported_tuples)) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..eda4f9d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,96 @@ +[tool.poetry] +requires-poetry = ">=2.0" + +[project] +name = "pylingual" +version = "0.0.1" +description = "A Python bytecode decompilation tool, supporting versions 3.6 - 3.13" +authors = [ {name = "syssec-utd"} ] +keywords = ["python", "decompilation", "pylingual", "reversing", "decompiler", "bytecode"] +license = "GPL-3.0-only" + +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "asttokens", + "datasets", + "huggingface-hub", + "matplotlib", + "networkx", + "numpy", + "pydot", + "requests", + "tokenizers", + "torch", + "tqdm", + "rich", + "seqeval", + "transformers==4.46.1", + "transformers[torch]", + "xdis", + "click" +] + +[project.urls] +homepage = "https://pylingual.io" + +[project.scripts] +pylingual = "pylingual.main:main" + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" + +# linter and formatter +[tool.ruff] +# Exclude commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] +target-version = "py311" +line-length = 240 +indent-width = 4 + +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F"] +ignore = [] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +line-ending = "auto" +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = true +docstring-code-line-length = "dynamic"