cobol-parser 2.0.1__tar.gz → 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cobol_parser-2.0.1/cobol_parser.egg-info → cobol_parser-3.0.0}/PKG-INFO +1 -1
- {cobol_parser-2.0.1 → cobol_parser-3.0.0/cobol_parser.egg-info}/PKG-INFO +1 -1
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/__init__.py +1 -1
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/codebase.py +75 -23
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/const_fold.py +274 -27
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/lint.py +81 -9
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/move_type_checker.py +297 -13
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/cli/parse.py +111 -7
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/compare.py +59 -5
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/decoding.py +39 -8
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/layout.py +155 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/values.py +44 -5
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/views.py +250 -54
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/preprocess/copy_statement_parser.py +5 -1
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/preprocess/path_resolver.py +65 -38
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/pyproject.toml +1 -1
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/LICENSE +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/MANIFEST.in +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/README.md +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobol_parser.egg-info/SOURCES.txt +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobol_parser.egg-info/dependency_links.txt +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobol_parser.egg-info/entry_points.txt +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobol_parser.egg-info/requires.txt +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobol_parser.egg-info/top_level.txt +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/__main__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_data_fast.c +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_data_fast.pyx +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_errors.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_lexer_fast.c +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_lexer_fast.pyx +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_lines.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_literals.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_preprocess_fast.c +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_preprocess_fast.pyx +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_source_directive.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/_storage.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/_files.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/_names.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/_procedures.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/_sql_flow.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/ast_diff.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/boundary_inventory.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/business_rules.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/complexity.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/constraints.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/crud_matrix.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/dclgen_check.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/dead_code.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/domains.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/field_lineage.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/host_var_resolver.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/ims_segment_analyzer.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/io_coverage_reporter.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/io_diagnostics.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/io_logger.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/io_sequence.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/language_env_analyzer.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/lineage_graph.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/lineage_jcl.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/linkage_checker.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_flow/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_flow/analyzer.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_flow/condition_text.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_flow/cycles.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_flow/edges.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_flow/graph.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_flow/models.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_flow/scanner.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_flow/statement_text.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/program_signature.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/recursion_risk.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/redundancy.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/sql_cics_lint.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/technical_debt.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/unused_variables.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/value_tracker.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/analysis/xref.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/batch.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/cli/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/cli/_project_config.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/cli/coverage.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/config/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/config/_yaml.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/config/project.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/core/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/core/ast_factory.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/core/exceptions.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/core/interfaces.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/core/registry.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/core/token_stream.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/dialects/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/dialects/ibm_enterprise.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/dialects/le_signatures.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/copybook_data.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/bundle.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/copybook.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/dataset.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/editing.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/encoding.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/errors.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/layout_offsets.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/options.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/program_layout.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/data/storage.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/flowchart.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/program_fixtures.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/scenarios.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/generators/verification.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/jcl/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/jcl/bindings.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/jcl/models.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/jcl/parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/lexer.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/ast_base.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/data_items.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/program.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/sql_statements.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/arithmetic.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/base.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/call.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/communication.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/control.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/data.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/io.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/legacy.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/misc.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/models/statements/report_writer.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/base.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/data.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/data_clauses.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/data_file_description.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/data_report_description.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/data_special_entries.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/environment.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/environment_sections.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/environment_select.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/factory.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/identification.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/procedure.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/procedure_boundaries.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/procedure_recovery.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/divisions/procedure_registry.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/_conditions.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/_utils.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/accept_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/allocate_free_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/arithmetic_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/call_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/close_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/communication_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/delete_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/display_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/entry_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/evaluate_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/exception_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/go_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/ibm_exec_statements.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/if_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/initialize_inspect_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/invoke_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/legacy_verb_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/move_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/open_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/perform_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/read_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/report_writer_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/rewrite_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/search_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/set_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/simple_statement_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/sort_merge_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/start_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/stop_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/string_unstring_parsers.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/unlock_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/write_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/parsers/statements/xml_json_parser.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/plugins/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/plugins/core.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/preprocess/__init__.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/preprocess/_normalize.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/preprocess/copybook_expander.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/preprocess/models.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/preprocess/replace_statement.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/preprocess/source_format.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/profiling.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/py.typed +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/cobolparser/scan.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/setup.cfg +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/setup.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/tests/test_all_samples.py +0 -0
- {cobol_parser-2.0.1 → cobol_parser-3.0.0}/tests/test_okf_staleness_map.py +0 -0
|
@@ -231,7 +231,7 @@ from .scan import (
|
|
|
231
231
|
CobolParseError = ParseError
|
|
232
232
|
|
|
233
233
|
# Version info
|
|
234
|
-
__version__ = "
|
|
234
|
+
__version__ = "3.0.0" # x-release-please-version
|
|
235
235
|
__author__ = "Samuel Dion"
|
|
236
236
|
__license__ = "AGPL-3.0-or-later"
|
|
237
237
|
|
|
@@ -14,7 +14,13 @@ and aggregates the results into a single graph with two kinds of edge:
|
|
|
14
14
|
targets, each marked ``resolved=False`` so the edge set is never mistaken
|
|
15
15
|
for an exhaustive one.
|
|
16
16
|
- **copy edges** -- ``program A copies member X``, and ``member X copies
|
|
17
|
-
member Y`` for nested copybooks.
|
|
17
|
+
member Y`` for nested copybooks. A DB2 ``EXEC SQL INCLUDE X END-EXEC`` is
|
|
18
|
+
a copy edge too, with the same ``COPY`` label and the same unresolved-member
|
|
19
|
+
reporting: it is how SQLCA and DCLGEN members are normally pulled in, so
|
|
20
|
+
leaving it out made "what uses this DCLGEN?" answer nothing (#295). The
|
|
21
|
+
output does not say which of the two produced an edge. An INCLUDE of
|
|
22
|
+
``SQLCA`` or ``SQLDA`` that is not on the path is not reported unresolved,
|
|
23
|
+
since the precompiler supplies those. These are collected by re-scanning each
|
|
18
24
|
source with :func:`~cobolparser.preprocess.copy_statement_parser.extract_copy_statements`
|
|
19
25
|
rather than from an expanded parse, because expansion *consumes* the COPY
|
|
20
26
|
statement and discards exactly the edge we are after. Members are resolved
|
|
@@ -84,6 +90,13 @@ __all__ = [
|
|
|
84
90
|
"ProgramNode",
|
|
85
91
|
]
|
|
86
92
|
|
|
93
|
+
#: Members the DB2 precompiler supplies itself. An ``EXEC SQL INCLUDE`` of one
|
|
94
|
+
#: that is not on the copybook path is normal, not a gap: it still gets its
|
|
95
|
+
#: edge, but no ``unresolved`` note. This is narrower than the copybook
|
|
96
|
+
#: expander, which lets *every* unresolvable INCLUDE through silently: here a
|
|
97
|
+
#: missing DCLGEN is still a gap worth reporting, and so is a COPY of either.
|
|
98
|
+
_PRECOMPILER_MEMBERS = frozenset({"SQLCA", "SQLDA"})
|
|
99
|
+
|
|
87
100
|
#: File extensions :meth:`CodebaseGraphBuilder.build_from_directory` treats as
|
|
88
101
|
#: COBOL program source. Matched case-insensitively.
|
|
89
102
|
DEFAULT_PROGRAM_EXTENSIONS: tuple[str, ...] = (".cbl", ".cob", ".cobol")
|
|
@@ -607,8 +620,13 @@ class CodebaseGraphBuilder:
|
|
|
607
620
|
)
|
|
608
621
|
# member -> resolved file path (None when it could not be resolved)
|
|
609
622
|
self._member_paths: dict[str, str | None] = {}
|
|
610
|
-
# member ->
|
|
611
|
-
self.
|
|
623
|
+
# member -> why it could not be resolved (only for failed members)
|
|
624
|
+
self._member_failures: dict[str, str] = {}
|
|
625
|
+
# (member, referrer) pairs already reported unresolved this build, so
|
|
626
|
+
# a COPY and an INCLUDE of one missing member give one note, not two
|
|
627
|
+
self._reported_failures: set[tuple[str, str]] = set()
|
|
628
|
+
# member -> its own direct (member name, is EXEC SQL INCLUDE), sorted
|
|
629
|
+
self._member_copies: dict[str, list[tuple[str, bool]]] = {}
|
|
612
630
|
|
|
613
631
|
# -- public API -------------------------------------------------------
|
|
614
632
|
|
|
@@ -617,6 +635,8 @@ class CodebaseGraphBuilder:
|
|
|
617
635
|
# Per-build caches: a copybook edited between two builds on one
|
|
618
636
|
# long-lived builder must not serve the previous build's chain.
|
|
619
637
|
self._member_paths = {}
|
|
638
|
+
self._member_failures = {}
|
|
639
|
+
self._reported_failures = set()
|
|
620
640
|
self._member_copies = {}
|
|
621
641
|
files = self._normalize_paths(paths)
|
|
622
642
|
|
|
@@ -923,7 +943,7 @@ class CodebaseGraphBuilder:
|
|
|
923
943
|
|
|
924
944
|
warnings: list[str] = []
|
|
925
945
|
statements = extract_copy_statements(
|
|
926
|
-
source, source_file=path, warnings=warnings
|
|
946
|
+
source, source_file=path, warnings=warnings, sql_include=True
|
|
927
947
|
)
|
|
928
948
|
for warning in warnings:
|
|
929
949
|
unresolved.append(f"{path}: COPY statement not usable: {warning}")
|
|
@@ -934,7 +954,11 @@ class CodebaseGraphBuilder:
|
|
|
934
954
|
if not member:
|
|
935
955
|
continue
|
|
936
956
|
member_path = self._resolve_member(
|
|
937
|
-
member,
|
|
957
|
+
member,
|
|
958
|
+
statement.library_qualifier,
|
|
959
|
+
path,
|
|
960
|
+
unresolved,
|
|
961
|
+
sql_include=statement.sql_include,
|
|
938
962
|
)
|
|
939
963
|
copy_edges.append(
|
|
940
964
|
CopyEdge(program=owner, member=member, path=member_path, nested=False)
|
|
@@ -949,27 +973,48 @@ class CodebaseGraphBuilder:
|
|
|
949
973
|
library: str | None,
|
|
950
974
|
referrer: str,
|
|
951
975
|
unresolved: list[str],
|
|
976
|
+
*,
|
|
977
|
+
sql_include: bool = False,
|
|
952
978
|
) -> str | None:
|
|
953
|
-
"""Resolve *member* to a file, caching the answer (and the failure).
|
|
979
|
+
"""Resolve *member* to a file, caching the answer (and the failure).
|
|
980
|
+
|
|
981
|
+
A failure is reported for every reference that is not an INCLUDE of
|
|
982
|
+
a precompiler-supplied member (:data:`_PRECOMPILER_MEMBERS`), so a
|
|
983
|
+
quiet ``INCLUDE SQLCA`` seen first cannot silence a later ``COPY
|
|
984
|
+
SQLCA``. One note per (member, referrer): whichever of COPY / INCLUDE
|
|
985
|
+
is seen first names it.
|
|
986
|
+
"""
|
|
954
987
|
if self._resolver is None:
|
|
955
988
|
return None
|
|
956
|
-
if member in self._member_paths:
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
989
|
+
if member not in self._member_paths:
|
|
990
|
+
try:
|
|
991
|
+
self._member_paths[member] = self._resolver.resolve(member, library)
|
|
992
|
+
except Exception as exc: # noqa: BLE001 -- an unresolvable member
|
|
993
|
+
# is a finding, not a crash: record why and carry on.
|
|
994
|
+
self._member_paths[member] = None
|
|
995
|
+
self._member_failures[member] = f"{type(exc).__name__}: {exc}"
|
|
996
|
+
resolved = self._member_paths[member]
|
|
997
|
+
if (
|
|
998
|
+
resolved is None
|
|
999
|
+
and not (sql_include and member in _PRECOMPILER_MEMBERS)
|
|
1000
|
+
and (member, referrer) not in self._reported_failures
|
|
1001
|
+
):
|
|
1002
|
+
self._reported_failures.add((member, referrer))
|
|
1003
|
+
kind = "EXEC SQL INCLUDE" if sql_include else "COPY"
|
|
963
1004
|
unresolved.append(
|
|
964
|
-
f"
|
|
965
|
-
|
|
1005
|
+
f"{kind} member {member} (referenced by {referrer}) could not be "
|
|
1006
|
+
"resolved on the copybook search path: "
|
|
1007
|
+
f"{self._member_failures[member]}"
|
|
966
1008
|
)
|
|
967
|
-
return None
|
|
968
|
-
self._member_paths[member] = resolved
|
|
969
1009
|
return resolved
|
|
970
1010
|
|
|
971
|
-
def _member_direct_copies(
|
|
972
|
-
|
|
1011
|
+
def _member_direct_copies(
|
|
1012
|
+
self, member: str, unresolved: list[str]
|
|
1013
|
+
) -> list[tuple[str, bool]]:
|
|
1014
|
+
"""What *member* itself COPYs or INCLUDEs (empty when unresolved).
|
|
1015
|
+
|
|
1016
|
+
Each entry is ``(member name, is EXEC SQL INCLUDE)``.
|
|
1017
|
+
"""
|
|
973
1018
|
if member in self._member_copies:
|
|
974
1019
|
return self._member_copies[member]
|
|
975
1020
|
self._member_copies[member] = []
|
|
@@ -985,13 +1030,13 @@ class CodebaseGraphBuilder:
|
|
|
985
1030
|
return []
|
|
986
1031
|
warnings: list[str] = []
|
|
987
1032
|
statements = extract_copy_statements(
|
|
988
|
-
source, source_file=member_path, warnings=warnings
|
|
1033
|
+
source, source_file=member_path, warnings=warnings, sql_include=True
|
|
989
1034
|
)
|
|
990
1035
|
for warning in warnings:
|
|
991
1036
|
unresolved.append(f"{member_path}: COPY statement not usable: {warning}")
|
|
992
1037
|
names = sorted(
|
|
993
1038
|
{
|
|
994
|
-
_normalize_member(s.member_name)
|
|
1039
|
+
(_normalize_member(s.member_name), s.sql_include)
|
|
995
1040
|
for s in statements
|
|
996
1041
|
if _normalize_member(s.member_name)
|
|
997
1042
|
}
|
|
@@ -1007,8 +1052,15 @@ class CodebaseGraphBuilder:
|
|
|
1007
1052
|
unresolved: list[str],
|
|
1008
1053
|
) -> None:
|
|
1009
1054
|
"""Emit nested member->member edges below *member*, cycle-safe."""
|
|
1010
|
-
|
|
1011
|
-
|
|
1055
|
+
seen: set[str] = set()
|
|
1056
|
+
for nested, sql_include in self._member_direct_copies(member, unresolved):
|
|
1057
|
+
nested_path = self._resolve_member(
|
|
1058
|
+
nested, None, member, unresolved, sql_include=sql_include
|
|
1059
|
+
)
|
|
1060
|
+
if nested in seen:
|
|
1061
|
+
# COPY and INCLUDE of one member: one edge, as for a program.
|
|
1062
|
+
continue
|
|
1063
|
+
seen.add(nested)
|
|
1012
1064
|
copy_edges.append(
|
|
1013
1065
|
CopyEdge(program=member, member=nested, path=nested_path, nested=True)
|
|
1014
1066
|
)
|
|
@@ -43,6 +43,11 @@ than guessing:
|
|
|
43
43
|
coercion. Real COBOL truncation rules for an overflowing MOVE are
|
|
44
44
|
well-defined, but replicating them exactly is out of scope for v1; an
|
|
45
45
|
overflowing result is poisoned instead of guessed.
|
|
46
|
+
* Every DISPLAY/WRITE in the program is a site: one inside an arm, loop or
|
|
47
|
+
handler records "unknown", and one the walk never reached (past a GO TO,
|
|
48
|
+
DECLARATIVES, dead code) vetoes the field at the end. A GO TO / EXIT
|
|
49
|
+
PARAGRAPH / NEXT SENTENCE the walk cannot follow puts it in "uncertain"
|
|
50
|
+
mode, where every later write poisons instead of folding.
|
|
46
51
|
* Only a bare ``DISPLAY``/``WRITE`` operand that resolves directly to an
|
|
47
52
|
elementary field is an observation point -- a whole-record WRITE's
|
|
48
53
|
individual member fields are not (yet) walked, so some real
|
|
@@ -78,11 +83,19 @@ from ..models.statements.control import (
|
|
|
78
83
|
GoStatement,
|
|
79
84
|
IfStatement,
|
|
80
85
|
PerformStatement,
|
|
86
|
+
SearchStatement,
|
|
81
87
|
StopStatement,
|
|
82
88
|
)
|
|
83
89
|
from ..models.statements.data import DisplayStatement, MoveStatement
|
|
84
90
|
from ..models.statements.io import IOAcceptStatement, IOReadStatement, IOWriteStatement
|
|
85
|
-
from ..models.statements.misc import
|
|
91
|
+
from ..models.statements.misc import (
|
|
92
|
+
ExitStatement,
|
|
93
|
+
GobackStatement,
|
|
94
|
+
InvokeStatement,
|
|
95
|
+
MergeStatement,
|
|
96
|
+
NextSentenceStatement,
|
|
97
|
+
SortStatement,
|
|
98
|
+
)
|
|
86
99
|
|
|
87
100
|
if TYPE_CHECKING:
|
|
88
101
|
# Same shape as constraints.py's own guard: cobolparser.generators
|
|
@@ -113,6 +126,13 @@ _SUFFIX_RE = re.compile(r"\s*\(.*$", re.DOTALL)
|
|
|
113
126
|
#: answer.
|
|
114
127
|
_MAX_WALK_STEPS = 50_000
|
|
115
128
|
|
|
129
|
+
#: `ordered_names` key prefix for a SECTION header (a marker, never a
|
|
130
|
+
#: paragraph) and for a paragraph with no name (statements written straight
|
|
131
|
+
#: under a SECTION header, or straight under PROCEDURE DIVISION). Neither
|
|
132
|
+
#: can collide with a COBOL name.
|
|
133
|
+
_SECTION_KEY = "\x00SECTION:"
|
|
134
|
+
_UNNAMED_KEY = "\x00UNNAMED:"
|
|
135
|
+
|
|
116
136
|
_TEXT_CATEGORIES = frozenset(
|
|
117
137
|
{FieldCategory.ALPHANUMERIC, FieldCategory.ALPHABETIC, FieldCategory.NATIONAL}
|
|
118
138
|
)
|
|
@@ -197,7 +217,7 @@ class _LayoutFacts:
|
|
|
197
217
|
share bytes through a REDEFINES chain, and which are JUSTIFIED. Built
|
|
198
218
|
once per index from the raw `CobolDataItem` trees and cached weakly."""
|
|
199
219
|
|
|
200
|
-
__slots__ = ("groups", "leaves", "aliases", "justified", "renames")
|
|
220
|
+
__slots__ = ("groups", "leaves", "aliases", "justified", "renames", "shared")
|
|
201
221
|
|
|
202
222
|
def __init__(self) -> None:
|
|
203
223
|
#: UPPERCASED bare group/record name -> qualified paths of every
|
|
@@ -213,6 +233,10 @@ class _LayoutFacts:
|
|
|
213
233
|
#: covers. A 66 is not a `CobolDataItem` (the index never resolves
|
|
214
234
|
#: it), so without this a write through the alias poisoned nothing.
|
|
215
235
|
self.renames: dict[str, list[str]] = {}
|
|
236
|
+
#: Leaves under a GLOBAL or EXTERNAL item: storage another program
|
|
237
|
+
#: (a contained program, or any program declaring the same EXTERNAL
|
|
238
|
+
#: name) can write, so every CALL / INVOKE may change them.
|
|
239
|
+
self.shared: set[str] = set()
|
|
216
240
|
|
|
217
241
|
def leaves_under(self, qpath: str) -> list[str]:
|
|
218
242
|
prefix = qpath + "."
|
|
@@ -333,18 +357,28 @@ def _data_children(item: CobolDataItem) -> list[CobolDataItem]:
|
|
|
333
357
|
|
|
334
358
|
|
|
335
359
|
def _collect_facts(
|
|
336
|
-
item: CobolDataItem,
|
|
360
|
+
item: CobolDataItem,
|
|
361
|
+
path: tuple[str, ...],
|
|
362
|
+
facts: _LayoutFacts,
|
|
363
|
+
shared: bool = False,
|
|
337
364
|
) -> None:
|
|
338
365
|
qpath = (*path, item.name)
|
|
339
366
|
qname = ".".join(qpath)
|
|
367
|
+
shared = (
|
|
368
|
+
shared
|
|
369
|
+
or bool(getattr(item, "is_global", False))
|
|
370
|
+
or bool(getattr(item, "is_external", False))
|
|
371
|
+
)
|
|
340
372
|
children = _data_children(item)
|
|
341
373
|
if children:
|
|
342
374
|
facts.groups.setdefault(item.name.upper(), []).append(qname)
|
|
343
375
|
for child in children:
|
|
344
|
-
_collect_facts(child, qpath, facts)
|
|
376
|
+
_collect_facts(child, qpath, facts, shared)
|
|
345
377
|
_link_redefines(children, qpath, facts)
|
|
346
378
|
return
|
|
347
379
|
facts.leaves.append(qname)
|
|
380
|
+
if shared:
|
|
381
|
+
facts.shared.add(qname)
|
|
348
382
|
if getattr(item, "justified", False):
|
|
349
383
|
facts.justified.add(qname)
|
|
350
384
|
|
|
@@ -543,6 +577,21 @@ class ConstantFolder:
|
|
|
543
577
|
# observed: no reported value could be shown to be the only one.
|
|
544
578
|
return ConstantFoldResult(known={})
|
|
545
579
|
|
|
580
|
+
# A DISPLAY/WRITE site the walk never reached (code after a GO TO,
|
|
581
|
+
# code only a GO TO reaches, a DECLARATIVES procedure, ...) may emit
|
|
582
|
+
# any value at all, so a field shown there is not knowable either --
|
|
583
|
+
# "every site that observed it" must be every site, not just the
|
|
584
|
+
# walked ones.
|
|
585
|
+
for site in _observation_sites(ast):
|
|
586
|
+
if id(site) in state.visited_sites:
|
|
587
|
+
continue
|
|
588
|
+
for operand in _site_operands(site):
|
|
589
|
+
resolved = _safe_resolve(index, operand)
|
|
590
|
+
if resolved is not None:
|
|
591
|
+
state.observed.setdefault(resolved.spec.qualified_name, []).append(
|
|
592
|
+
None
|
|
593
|
+
)
|
|
594
|
+
|
|
546
595
|
# A field is reported only when every DISPLAY/WRITE site that
|
|
547
596
|
# observed it saw the same known value -- the value the program
|
|
548
597
|
# actually emitted there, not whatever it held when the walk ended.
|
|
@@ -563,12 +612,64 @@ def _fall_through_chain(
|
|
|
563
612
|
ordered_names: list[str],
|
|
564
613
|
by_name: dict[str, CobolParagraph],
|
|
565
614
|
) -> list[CobolParagraph]:
|
|
566
|
-
"""*entry* followed by every paragraph after it in source order
|
|
567
|
-
|
|
568
|
-
|
|
615
|
+
"""*entry* followed by every paragraph after it in source order (across
|
|
616
|
+
SECTION boundaries: control falls into the next section too)."""
|
|
617
|
+
start = next(
|
|
618
|
+
(i for i, key in enumerate(ordered_names) if by_name.get(key) is entry),
|
|
619
|
+
None,
|
|
620
|
+
)
|
|
621
|
+
if start is None:
|
|
569
622
|
return [entry]
|
|
570
|
-
start
|
|
571
|
-
|
|
623
|
+
return [by_name[key] for key in ordered_names[start:] if key in by_name]
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _observation_sites(ast: CobolProgram) -> list[Any]:
|
|
627
|
+
"""Every DISPLAY and WRITE statement of the PROCEDURE DIVISION, nested
|
|
628
|
+
ones (IF/EVALUATE/SEARCH arms, inline PERFORM bodies, handlers)
|
|
629
|
+
included."""
|
|
630
|
+
procedure = ast.get_division("PROCEDURE")
|
|
631
|
+
if procedure is None:
|
|
632
|
+
return []
|
|
633
|
+
pending: list[list[Any]] = [list(p.statements) for p in procedure.paragraphs]
|
|
634
|
+
for section in procedure._sections.values():
|
|
635
|
+
pending.extend(list(p.statements) for p in section.paragraphs)
|
|
636
|
+
sites: list[Any] = []
|
|
637
|
+
seen: set[int] = set()
|
|
638
|
+
while pending:
|
|
639
|
+
for stmt in pending.pop():
|
|
640
|
+
if id(stmt) in seen:
|
|
641
|
+
continue
|
|
642
|
+
seen.add(id(stmt))
|
|
643
|
+
if isinstance(stmt, (DisplayStatement, IOWriteStatement)):
|
|
644
|
+
sites.append(stmt)
|
|
645
|
+
pending.extend(_nested_statement_lists(stmt))
|
|
646
|
+
return sites
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def _site_operands(stmt: Any) -> list[str]:
|
|
650
|
+
"""The operands `_dispatch_one` marks observed for a DISPLAY/WRITE."""
|
|
651
|
+
if isinstance(stmt, DisplayStatement):
|
|
652
|
+
return list(stmt.display_items)
|
|
653
|
+
if isinstance(stmt, IOWriteStatement) and stmt.record_name:
|
|
654
|
+
return [stmt.record_name]
|
|
655
|
+
return []
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _transfer_kind(stmt: Any) -> str | None:
|
|
659
|
+
"""``"jump"`` for a GO TO (control goes to code this walk does not
|
|
660
|
+
follow), ``"skip"`` for a transfer that only skips forward inside the
|
|
661
|
+
current paragraph / section / inline PERFORM / sentence, else None."""
|
|
662
|
+
if isinstance(stmt, GoStatement):
|
|
663
|
+
return "jump"
|
|
664
|
+
if isinstance(stmt, NextSentenceStatement):
|
|
665
|
+
return "skip"
|
|
666
|
+
if isinstance(stmt, ExitStatement) and (stmt.exit_type or "") in {
|
|
667
|
+
"PARAGRAPH",
|
|
668
|
+
"SECTION",
|
|
669
|
+
"PERFORM",
|
|
670
|
+
}:
|
|
671
|
+
return "skip"
|
|
672
|
+
return None
|
|
572
673
|
|
|
573
674
|
|
|
574
675
|
# -- walk state ---------------------------------------------------------------
|
|
@@ -584,6 +685,8 @@ class _WalkState:
|
|
|
584
685
|
"accepted",
|
|
585
686
|
"steps",
|
|
586
687
|
"exhausted",
|
|
688
|
+
"visited_sites",
|
|
689
|
+
"uncertain",
|
|
587
690
|
)
|
|
588
691
|
|
|
589
692
|
def __init__(
|
|
@@ -610,6 +713,15 @@ class _WalkState:
|
|
|
610
713
|
self.steps = 0
|
|
611
714
|
#: Set when the budget ran out; `fold()` then reports nothing.
|
|
612
715
|
self.exhausted = False
|
|
716
|
+
#: ``id()`` of every DISPLAY/WRITE the walk reached (main line or
|
|
717
|
+
#: nested), so `fold()` can tell which sites it never saw.
|
|
718
|
+
self.visited_sites: set[int] = set()
|
|
719
|
+
#: Set once a branch arm / loop body / handler could GO TO, EXIT
|
|
720
|
+
#: PARAGRAPH/SECTION/PERFORM or NEXT SENTENCE (or the main line does
|
|
721
|
+
#: an EXIT PARAGRAPH/... itself): from then on the walk cannot tell
|
|
722
|
+
#: whether a later statement really ran, so every write it meets
|
|
723
|
+
#: poisons instead of folding.
|
|
724
|
+
self.uncertain = False
|
|
613
725
|
|
|
614
726
|
|
|
615
727
|
# -- entry point / paragraph ordering -----------------------------------------
|
|
@@ -657,11 +769,15 @@ def _ordered_paragraphs(
|
|
|
657
769
|
by_name: dict[str, CobolParagraph] = {}
|
|
658
770
|
|
|
659
771
|
def register(para: CobolParagraph) -> None:
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
772
|
+
# A paragraph with no name (statements straight under a SECTION
|
|
773
|
+
# header) still runs when control falls into it or its section is
|
|
774
|
+
# PERFORMed, so it gets a key no PERFORM can name.
|
|
775
|
+
name = (para.paragraph_name or "").upper()
|
|
776
|
+
if not name:
|
|
777
|
+
name = f"{_UNNAMED_KEY}{len(ordered)}"
|
|
778
|
+
if name not in by_name:
|
|
779
|
+
ordered.append(name)
|
|
780
|
+
by_name[name] = para
|
|
665
781
|
|
|
666
782
|
top_level_items: list[Any] = sorted(
|
|
667
783
|
[*procedure._sections.values(), *procedure.paragraphs],
|
|
@@ -671,6 +787,10 @@ def _ordered_paragraphs(
|
|
|
671
787
|
if isinstance(item, CobolParagraph):
|
|
672
788
|
register(item)
|
|
673
789
|
else:
|
|
790
|
+
# A marker (absent from `by_name`) so `PERFORM SEC-A` / `THRU
|
|
791
|
+
# SEC-B` can find where a section's paragraphs start and end.
|
|
792
|
+
section_name = (getattr(item, "section_name", "") or "").upper()
|
|
793
|
+
ordered.append(f"{_SECTION_KEY}{section_name}")
|
|
674
794
|
for para in item.paragraphs:
|
|
675
795
|
register(para)
|
|
676
796
|
return ordered, by_name
|
|
@@ -761,6 +881,27 @@ def _dispatch_one(
|
|
|
761
881
|
) -> tuple[str | None, list[Any]] | None:
|
|
762
882
|
"""Per-statement-type fold dispatch for `_walk_one` (which additionally
|
|
763
883
|
poisons conditional handler bodies around whatever happens here)."""
|
|
884
|
+
if state.uncertain and isinstance(
|
|
885
|
+
stmt,
|
|
886
|
+
(
|
|
887
|
+
MoveStatement,
|
|
888
|
+
ArithmeticStatement,
|
|
889
|
+
ComputeStatement,
|
|
890
|
+
IOReadStatement,
|
|
891
|
+
IOAcceptStatement,
|
|
892
|
+
),
|
|
893
|
+
):
|
|
894
|
+
# Past a possible skip/jump: this write may or may not have run.
|
|
895
|
+
_poison_written_names(
|
|
896
|
+
[stmt], index, state.env, by_name, ordered_names, state=state
|
|
897
|
+
)
|
|
898
|
+
return None
|
|
899
|
+
if _transfer_kind(stmt) == "skip":
|
|
900
|
+
# EXIT PARAGRAPH / SECTION / PERFORM, NEXT SENTENCE on the main
|
|
901
|
+
# line: the statements after it are skipped, which this straight-line
|
|
902
|
+
# walk does not model.
|
|
903
|
+
state.uncertain = True
|
|
904
|
+
return None
|
|
764
905
|
if isinstance(stmt, MoveStatement):
|
|
765
906
|
_fold_move(stmt, index, state.env)
|
|
766
907
|
elif isinstance(stmt, AddStatement):
|
|
@@ -774,10 +915,14 @@ def _dispatch_one(
|
|
|
774
915
|
elif isinstance(stmt, ComputeStatement):
|
|
775
916
|
_fold_compute(stmt, index, state.env)
|
|
776
917
|
elif isinstance(stmt, DisplayStatement):
|
|
918
|
+
state.visited_sites.add(id(stmt))
|
|
777
919
|
for item in stmt.display_items:
|
|
778
920
|
_mark_observed(item, index, state)
|
|
779
921
|
elif isinstance(stmt, IOWriteStatement):
|
|
780
|
-
|
|
922
|
+
state.visited_sites.add(id(stmt))
|
|
923
|
+
if stmt.record_name and stmt.from_clause and state.uncertain:
|
|
924
|
+
_poison_names([stmt.record_name], index, state.env)
|
|
925
|
+
elif stmt.record_name and stmt.from_clause:
|
|
781
926
|
# `WRITE rec FROM id` is `MOVE id TO rec` and then the WRITE. A
|
|
782
927
|
# group record target is poisoned whole by `_apply_move_target`.
|
|
783
928
|
source = _resolve_move_source(stmt.from_clause, index, state.env)
|
|
@@ -832,7 +977,11 @@ def _dispatch_one(
|
|
|
832
977
|
# values with seedable IN ones, and the raw AST does not carry
|
|
833
978
|
# resolved direction to tell them apart (only
|
|
834
979
|
# `BoundaryInventoryAnalyzer`'s own richer analysis does).
|
|
835
|
-
|
|
980
|
+
# Nested bodies (SEARCH WHEN, ...) and out-of-line procedures
|
|
981
|
+
# (SORT/MERGE INPUT/OUTPUT PROCEDURE) are poisoned the same way.
|
|
982
|
+
_poison_written_names(
|
|
983
|
+
[stmt], index, state.env, by_name, ordered_names, state=state
|
|
984
|
+
)
|
|
836
985
|
return None
|
|
837
986
|
|
|
838
987
|
|
|
@@ -943,6 +1092,11 @@ def _poison_statement_writes(
|
|
|
943
1092
|
base, _ = _base_name(stmt.target)
|
|
944
1093
|
state.accepted.add(base.upper())
|
|
945
1094
|
_poison_names(_write_targets(stmt), index, env)
|
|
1095
|
+
if isinstance(stmt, (CallStatement, InvokeStatement)):
|
|
1096
|
+
# The called program may write any GLOBAL item (a contained program
|
|
1097
|
+
# sees them by name) and any EXTERNAL one, USING list or not.
|
|
1098
|
+
for leaf in _facts(index).shared:
|
|
1099
|
+
_drop(leaf, index, env)
|
|
946
1100
|
|
|
947
1101
|
|
|
948
1102
|
def _mark_observed(operand: str, index: ProgramLayoutIndex, state: _WalkState) -> None:
|
|
@@ -977,6 +1131,9 @@ def _walk_perform(
|
|
|
977
1131
|
body = stmt.inline_statements or _perform_body_statements(
|
|
978
1132
|
stmt, by_name, ordered_names
|
|
979
1133
|
)
|
|
1134
|
+
if body is None:
|
|
1135
|
+
state.env.clear() # an unknown target could have written anything
|
|
1136
|
+
return None
|
|
980
1137
|
_poison_written_names(
|
|
981
1138
|
body, index, state.env, by_name, ordered_names, state=state
|
|
982
1139
|
)
|
|
@@ -989,25 +1146,60 @@ def _walk_perform(
|
|
|
989
1146
|
return None
|
|
990
1147
|
target = stmt.target_paragraph.upper()
|
|
991
1148
|
body = _perform_body_statements(stmt, by_name, ordered_names)
|
|
1149
|
+
if body is None:
|
|
1150
|
+
state.env.clear() # an unknown target could have written anything
|
|
1151
|
+
return None
|
|
992
1152
|
return (target, body)
|
|
993
1153
|
|
|
994
1154
|
|
|
1155
|
+
def _procedure_span(name: str, ordered_names: list[str], *, last: bool) -> int | None:
|
|
1156
|
+
"""Index in *ordered_names* where the paragraph or SECTION *name* starts
|
|
1157
|
+
(or, with *last*, ends). A paragraph name wins over a section name."""
|
|
1158
|
+
if name in ordered_names:
|
|
1159
|
+
return ordered_names.index(name)
|
|
1160
|
+
marker = f"{_SECTION_KEY}{name}"
|
|
1161
|
+
if marker not in ordered_names:
|
|
1162
|
+
return None
|
|
1163
|
+
start = ordered_names.index(marker)
|
|
1164
|
+
if not last:
|
|
1165
|
+
return start
|
|
1166
|
+
end = start
|
|
1167
|
+
for idx in range(start + 1, len(ordered_names)):
|
|
1168
|
+
if ordered_names[idx].startswith(_SECTION_KEY):
|
|
1169
|
+
break
|
|
1170
|
+
end = idx
|
|
1171
|
+
return end
|
|
1172
|
+
|
|
1173
|
+
|
|
995
1174
|
def _perform_body_statements(
|
|
996
1175
|
stmt: PerformStatement,
|
|
997
1176
|
by_name: dict[str, CobolParagraph],
|
|
998
1177
|
ordered_names: list[str],
|
|
999
|
-
) -> list[Any]:
|
|
1178
|
+
) -> list[Any] | None:
|
|
1179
|
+
"""The statements a PERFORM of a paragraph, a SECTION or a THRU range
|
|
1180
|
+
executes, or None when the target cannot be found -- the caller must
|
|
1181
|
+
then assume the PERFORM could have written anything."""
|
|
1000
1182
|
if not stmt.target_paragraph:
|
|
1001
|
-
return
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1183
|
+
return None
|
|
1184
|
+
return _range_statements(
|
|
1185
|
+
stmt.target_paragraph, stmt.thru_paragraph, by_name, ordered_names
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
def _range_statements(
|
|
1190
|
+
first: str,
|
|
1191
|
+
thru: str | None,
|
|
1192
|
+
by_name: dict[str, CobolParagraph],
|
|
1193
|
+
ordered_names: list[str],
|
|
1194
|
+
) -> list[Any] | None:
|
|
1195
|
+
"""The statements of procedure *first* (THRU *thru*), or None when it
|
|
1196
|
+
cannot be found."""
|
|
1197
|
+
start = first.upper()
|
|
1198
|
+
end = (thru or first).upper()
|
|
1199
|
+
start_idx = _procedure_span(start, ordered_names, last=False)
|
|
1200
|
+
end_idx = _procedure_span(end, ordered_names, last=True)
|
|
1201
|
+
if start_idx is None or end_idx is None or end_idx < start_idx:
|
|
1202
|
+
return None # unknown target / malformed THRU order: not a guess
|
|
1011
1203
|
statements: list[Any] = []
|
|
1012
1204
|
for name in ordered_names[start_idx : end_idx + 1]:
|
|
1013
1205
|
para = by_name.get(name)
|
|
@@ -1089,9 +1281,31 @@ def _nested_statement_lists(stmt: Any) -> list[list[Any]]:
|
|
|
1089
1281
|
lists.append(wc.statements)
|
|
1090
1282
|
if stmt.default_when:
|
|
1091
1283
|
lists.append(stmt.default_when.statements)
|
|
1284
|
+
elif isinstance(stmt, SearchStatement):
|
|
1285
|
+
# SEARCH's WHEN clauses are dicts: {"condition", "statements"}.
|
|
1286
|
+
for clause in stmt.when_clauses:
|
|
1287
|
+
body = clause.get("statements") if isinstance(clause, dict) else None
|
|
1288
|
+
if isinstance(body, list):
|
|
1289
|
+
lists.append(body)
|
|
1092
1290
|
return lists
|
|
1093
1291
|
|
|
1094
1292
|
|
|
1293
|
+
def _procedure_ranges(stmt: Any) -> list[tuple[str, str | None]]:
|
|
1294
|
+
"""Out-of-line procedures a non-PERFORM statement runs: a SORT/MERGE
|
|
1295
|
+
INPUT / OUTPUT PROCEDURE (optionally THRU)."""
|
|
1296
|
+
ranges: list[tuple[str, str | None]] = []
|
|
1297
|
+
if isinstance(stmt, (SortStatement, MergeStatement)):
|
|
1298
|
+
for first_attr, thru_attr in (
|
|
1299
|
+
("input_procedure", "input_procedure_thru"),
|
|
1300
|
+
("output_procedure", "output_procedure_thru"),
|
|
1301
|
+
):
|
|
1302
|
+
first = getattr(stmt, first_attr, None)
|
|
1303
|
+
if isinstance(first, str) and first:
|
|
1304
|
+
thru = getattr(stmt, thru_attr, None)
|
|
1305
|
+
ranges.append((first, thru if isinstance(thru, str) else None))
|
|
1306
|
+
return ranges
|
|
1307
|
+
|
|
1308
|
+
|
|
1095
1309
|
def _poison_written_names(
|
|
1096
1310
|
statements: list[Any],
|
|
1097
1311
|
index: ProgramLayoutIndex,
|
|
@@ -1135,10 +1349,43 @@ def _poison_written_names(
|
|
|
1135
1349
|
if not target or target in visiting:
|
|
1136
1350
|
continue # non-looping PERFORM cycle: stop rather than recurse forever
|
|
1137
1351
|
body = _perform_body_statements(stmt, by_name, ordered_names)
|
|
1352
|
+
if body is None:
|
|
1353
|
+
env.clear() # an unknown target could have written anything
|
|
1354
|
+
continue
|
|
1138
1355
|
visiting.add(target)
|
|
1139
1356
|
frames.append((iter(body), target))
|
|
1140
1357
|
continue
|
|
1358
|
+
if state is not None:
|
|
1359
|
+
if isinstance(stmt, (DisplayStatement, IOWriteStatement)):
|
|
1360
|
+
# A site inside a block that may run zero, one or many times:
|
|
1361
|
+
# what it shows is not knowable, so it vetoes the field.
|
|
1362
|
+
state.visited_sites.add(id(stmt))
|
|
1363
|
+
for operand in _site_operands(stmt):
|
|
1364
|
+
resolved = _safe_resolve(index, operand)
|
|
1365
|
+
if resolved is not None:
|
|
1366
|
+
state.observed.setdefault(
|
|
1367
|
+
resolved.spec.qualified_name, []
|
|
1368
|
+
).append(None)
|
|
1369
|
+
transfer = _transfer_kind(stmt)
|
|
1370
|
+
if transfer is not None:
|
|
1371
|
+
# The block may leave the straight line: later statements may
|
|
1372
|
+
# not run, and a GO TO runs code this walk never sees.
|
|
1373
|
+
state.uncertain = True
|
|
1374
|
+
if transfer == "jump":
|
|
1375
|
+
env.clear()
|
|
1141
1376
|
_poison_statement_writes(stmt, index, env, state=state)
|
|
1377
|
+
for first, thru in _procedure_ranges(stmt):
|
|
1378
|
+
# A SORT/MERGE INPUT/OUTPUT PROCEDURE runs once per record: a
|
|
1379
|
+
# loop body, poisoned like one.
|
|
1380
|
+
key = first.upper()
|
|
1381
|
+
if key in visiting:
|
|
1382
|
+
continue
|
|
1383
|
+
body = _range_statements(first, thru, by_name, ordered_names)
|
|
1384
|
+
if body is None:
|
|
1385
|
+
env.clear()
|
|
1386
|
+
continue
|
|
1387
|
+
visiting.add(key)
|
|
1388
|
+
frames.append((iter(body), key))
|
|
1142
1389
|
for nested in _nested_statement_lists(stmt):
|
|
1143
1390
|
_poison_written_names(
|
|
1144
1391
|
nested, index, env, by_name, ordered_names, visiting, state=state
|