cobol-parser 3.2.1__tar.gz → 3.3.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-3.2.1/cobol_parser.egg-info → cobol_parser-3.3.0}/PKG-INFO +4 -4
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/README.md +2 -2
- {cobol_parser-3.2.1 → cobol_parser-3.3.0/cobol_parser.egg-info}/PKG-INFO +4 -4
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/SOURCES.txt +6 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/requires.txt +1 -1
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/__init__.py +1 -1
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_sql_flow.py +68 -11
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/boundary_inventory.py +9 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/constraints.py +346 -1
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/value_tracker.py +321 -4
- cobol_parser-3.3.0/cobolparser/definitions/__init__.py +134 -0
- cobol_parser-3.3.0/cobolparser/definitions/_asm.py +269 -0
- cobol_parser-3.3.0/cobolparser/definitions/bms.py +269 -0
- cobol_parser-3.3.0/cobolparser/definitions/csd.py +484 -0
- cobol_parser-3.3.0/cobolparser/definitions/ddl.py +759 -0
- cobol_parser-3.3.0/cobolparser/definitions/ims.py +407 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/program_layout.py +84 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/program_fixtures.py +64 -11
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/scenarios.py +274 -7
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/claims.py +297 -20
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/jcl_tools.py +7 -1
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/link_decks.py +66 -34
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/slicing.py +192 -91
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/sort_layout.py +148 -16
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/ibm_exec_statements.py +9 -1
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/pyproject.toml +2 -2
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/LICENSE +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/MANIFEST.in +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/dependency_links.txt +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/entry_points.txt +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/top_level.txt +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/__main__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_accel.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_data_fast.c +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_data_fast.pyx +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_errors.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_facts_cache.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_lexer_fast.c +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_lexer_fast.pyx +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_lines.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_literals.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_preprocess_fast.c +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_preprocess_fast.pyx +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_source_directive.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_storage.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_cics.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_files.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_names.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_procedures.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_sql_whenever.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/ast_diff.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/business_rules.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/call_interface.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/codebase.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/complexity.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/const_fold.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/crud_matrix.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/dclgen_check.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/dead_code.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/domains.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/field_lineage.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/field_widening.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/host_var_resolver.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/ims_segment_analyzer.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/invocation_counts.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/io_coverage_reporter.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/io_diagnostics.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/io_logger.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/io_sequence.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/language_env_analyzer.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/lineage_graph.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/lineage_jcl.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/linkage_checker.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/lint.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/move_type_checker.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/analyzer.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/condition_text.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/cycles.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/edges.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/graph.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/models.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/scanner.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/statement_text.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_signature.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/recursion_risk.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/redundancy.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/sarif.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/sql_cics_lint.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/technical_debt.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/unused_variables.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/xref.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/batch.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/cli/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/cli/_project_config.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/cli/coverage.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/cli/parse.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/config/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/config/_yaml.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/config/project.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/ast_factory.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/exceptions.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/interfaces.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/registry.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/token_stream.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/dialects/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/dialects/ibm_enterprise.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/dialects/le_signatures.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/copybook_data.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/bundle.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/compare.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/copybook.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/dataset.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/decoding.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/editing.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/encoding.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/errors.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/layout.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/layout_offsets.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/options.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/storage.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/values.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/views.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/flowchart.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/verification.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/bindings.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/models.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/programs.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/lexer.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/codebase_index.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/envelope.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/facts.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/hints.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/languages.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/navigation.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/prompts.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/server.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/tools.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/workspace.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/ast_base.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/data_items.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/program.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/sql_statements.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/arithmetic.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/base.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/call.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/communication.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/control.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/data.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/io.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/legacy.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/misc.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/report_writer.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/base.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data_clauses.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data_file_description.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data_report_description.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data_special_entries.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/environment.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/environment_sections.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/environment_select.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/factory.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/identification.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/procedure.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/procedure_boundaries.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/procedure_recovery.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/procedure_registry.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/_conditions.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/_utils.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/accept_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/allocate_free_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/arithmetic_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/call_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/close_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/communication_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/delete_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/display_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/entry_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/evaluate_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/exception_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/go_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/if_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/initialize_inspect_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/invoke_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/legacy_verb_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/move_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/open_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/perform_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/read_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/report_writer_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/rewrite_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/search_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/set_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/simple_statement_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/sort_merge_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/start_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/stop_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/string_unstring_parsers.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/unlock_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/write_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/xml_json_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/plugins/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/plugins/core.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/__init__.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/_normalize.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/copy_statement_parser.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/copybook_expander.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/models.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/path_resolver.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/replace_statement.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/source_format.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/profiling.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/py.typed +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/scan.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/setup.cfg +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/setup.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/tests/test_all_samples.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/tests/test_okf_claims.py +0 -0
- {cobol_parser-3.2.1 → cobol_parser-3.3.0}/tests/test_okf_staleness_map.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cobol-parser
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: A comprehensive COBOL parser for Python with IBM Enterprise COBOL focus
|
|
5
5
|
Author-email: Samuel Dion <sam94dion@gmail.com>
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
@@ -28,7 +28,7 @@ Requires-Dist: jcl-parser>=2.0.0; extra == "jcl"
|
|
|
28
28
|
Provides-Extra: pl1
|
|
29
29
|
Requires-Dist: landparty-pl1-parser>=2.0.0; extra == "pl1"
|
|
30
30
|
Provides-Extra: hlasm
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: hlasm-parser>=0.2.2; extra == "hlasm"
|
|
32
32
|
Provides-Extra: mcp
|
|
33
33
|
Requires-Dist: mcp<2,>=1.10; extra == "mcp"
|
|
34
34
|
Provides-Extra: dev
|
|
@@ -289,8 +289,8 @@ python -m black .
|
|
|
289
289
|
python -m mypy cobolparser/
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
-
CI
|
|
293
|
-
run lint, types and the full suite locally before pushing. Measure coverage on
|
|
292
|
+
CI runs only on manual dispatch (`.github/workflows/ci.yml` has no push or PR
|
|
293
|
+
trigger), so run lint, types and the full suite locally before pushing. Measure coverage on
|
|
294
294
|
the pure-Python build: with `COBOLPARSER_ACCEL=1`, coverage.py cannot see the
|
|
295
295
|
code the compiled modules replace. Test layout and markers:
|
|
296
296
|
[.okf/tests.md](.okf/tests.md).
|
|
@@ -239,8 +239,8 @@ python -m black .
|
|
|
239
239
|
python -m mypy cobolparser/
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
-
CI
|
|
243
|
-
run lint, types and the full suite locally before pushing. Measure coverage on
|
|
242
|
+
CI runs only on manual dispatch (`.github/workflows/ci.yml` has no push or PR
|
|
243
|
+
trigger), so run lint, types and the full suite locally before pushing. Measure coverage on
|
|
244
244
|
the pure-Python build: with `COBOLPARSER_ACCEL=1`, coverage.py cannot see the
|
|
245
245
|
code the compiled modules replace. Test layout and markers:
|
|
246
246
|
[.okf/tests.md](.okf/tests.md).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cobol-parser
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: A comprehensive COBOL parser for Python with IBM Enterprise COBOL focus
|
|
5
5
|
Author-email: Samuel Dion <sam94dion@gmail.com>
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
@@ -28,7 +28,7 @@ Requires-Dist: jcl-parser>=2.0.0; extra == "jcl"
|
|
|
28
28
|
Provides-Extra: pl1
|
|
29
29
|
Requires-Dist: landparty-pl1-parser>=2.0.0; extra == "pl1"
|
|
30
30
|
Provides-Extra: hlasm
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: hlasm-parser>=0.2.2; extra == "hlasm"
|
|
32
32
|
Provides-Extra: mcp
|
|
33
33
|
Requires-Dist: mcp<2,>=1.10; extra == "mcp"
|
|
34
34
|
Provides-Extra: dev
|
|
@@ -289,8 +289,8 @@ python -m black .
|
|
|
289
289
|
python -m mypy cobolparser/
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
-
CI
|
|
293
|
-
run lint, types and the full suite locally before pushing. Measure coverage on
|
|
292
|
+
CI runs only on manual dispatch (`.github/workflows/ci.yml` has no push or PR
|
|
293
|
+
trigger), so run lint, types and the full suite locally before pushing. Measure coverage on
|
|
294
294
|
the pure-Python build: with `COBOLPARSER_ACCEL=1`, coverage.py cannot see the
|
|
295
295
|
code the compiled modules replace. Test layout and markers:
|
|
296
296
|
[.okf/tests.md](.okf/tests.md).
|
|
@@ -95,6 +95,12 @@ cobolparser/core/exceptions.py
|
|
|
95
95
|
cobolparser/core/interfaces.py
|
|
96
96
|
cobolparser/core/registry.py
|
|
97
97
|
cobolparser/core/token_stream.py
|
|
98
|
+
cobolparser/definitions/__init__.py
|
|
99
|
+
cobolparser/definitions/_asm.py
|
|
100
|
+
cobolparser/definitions/bms.py
|
|
101
|
+
cobolparser/definitions/csd.py
|
|
102
|
+
cobolparser/definitions/ddl.py
|
|
103
|
+
cobolparser/definitions/ims.py
|
|
98
104
|
cobolparser/dialects/__init__.py
|
|
99
105
|
cobolparser/dialects/ibm_enterprise.py
|
|
100
106
|
cobolparser/dialects/le_signatures.py
|
|
@@ -231,7 +231,7 @@ from .scan import (
|
|
|
231
231
|
CobolParseError = ParseError
|
|
232
232
|
|
|
233
233
|
# Version info
|
|
234
|
-
__version__ = "3.
|
|
234
|
+
__version__ = "3.3.0" # x-release-please-version
|
|
235
235
|
__author__ = "Samuel Dion"
|
|
236
236
|
__license__ = "AGPL-3.0-or-later"
|
|
237
237
|
|
|
@@ -181,8 +181,16 @@ def host_var_name(raw: str) -> str:
|
|
|
181
181
|
return " OF ".join(reversed(parts))
|
|
182
182
|
|
|
183
183
|
|
|
184
|
+
#: A DB2 delimited identifier that is an ordinary name once unquoted.
|
|
185
|
+
_SIMPLE_DELIMITED_RE = re.compile(r'"([A-Z#@$][A-Z0-9_#@$]*)"')
|
|
186
|
+
|
|
187
|
+
|
|
184
188
|
def _normalize(sql: str) -> str:
|
|
185
189
|
text = _STRING_RE.sub("''", sql.upper())
|
|
190
|
+
# ``"TAB4"`` is the table TAB4, as ExecSQLStatement.tables reads it. A
|
|
191
|
+
# delimited name that is no ordinary name (``"MY TAB"``) keeps its
|
|
192
|
+
# quotes, so it is skipped with a caveat rather than cut at its blank.
|
|
193
|
+
text = _SIMPLE_DELIMITED_RE.sub(r"\1", text)
|
|
186
194
|
return " ".join(text.split())
|
|
187
195
|
|
|
188
196
|
|
|
@@ -306,8 +314,34 @@ def _columns_in(expr: str) -> list[tuple[str | None, str]]:
|
|
|
306
314
|
return out
|
|
307
315
|
|
|
308
316
|
|
|
317
|
+
def _skipped_correlation(piece: str) -> str | None:
|
|
318
|
+
"""The correlation name after a parenthesised FROM item (a sub-select,
|
|
319
|
+
``TABLE(...)`` or ``FINAL TABLE (...)``): ``(...) AS X`` -> ``X``."""
|
|
320
|
+
if "(" not in piece:
|
|
321
|
+
return None
|
|
322
|
+
_inner, after = _balanced(piece, piece.index("("))
|
|
323
|
+
words = piece[after:].split()
|
|
324
|
+
if words and words[0] == "AS":
|
|
325
|
+
words = words[1:]
|
|
326
|
+
if words:
|
|
327
|
+
# ``AS T(V)``: a correlation name followed by its column list.
|
|
328
|
+
words[0] = words[0].split("(", 1)[0]
|
|
329
|
+
if (
|
|
330
|
+
words
|
|
331
|
+
and words[0] not in _SQL_WORDS
|
|
332
|
+
and re.fullmatch(r"[A-Z0-9_#@$-]+", words[0])
|
|
333
|
+
):
|
|
334
|
+
return words[0]
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
|
|
309
338
|
def _from_tables(from_body: str) -> tuple[dict[str, str], list[str], list[str]]:
|
|
310
|
-
"""alias -> table, the table list in order, and caveats.
|
|
339
|
+
"""alias -> table, the table list in order, and caveats.
|
|
340
|
+
|
|
341
|
+
A FROM item that is not traced (a sub-select, a table function, a
|
|
342
|
+
data-change reference) maps its correlation name to ``""``, so a column
|
|
343
|
+
qualified by it is not mistaken for a column of a table of that name.
|
|
344
|
+
"""
|
|
311
345
|
caveats: list[str] = []
|
|
312
346
|
aliases: dict[str, str] = {}
|
|
313
347
|
tables: list[str] = []
|
|
@@ -318,16 +352,31 @@ def _from_tables(from_body: str) -> tuple[dict[str, str], list[str], list[str]]:
|
|
|
318
352
|
piece = re.split(r"\bON\b", piece, maxsplit=1)[0].strip()
|
|
319
353
|
if not piece:
|
|
320
354
|
continue
|
|
355
|
+
skipped = None
|
|
321
356
|
if piece.startswith("("):
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if re.match(r"(?:FINAL|NEW|OLD)\s+TABLE\s*\(", piece):
|
|
357
|
+
skipped = "a sub-select in FROM is not traced"
|
|
358
|
+
elif re.match(r"(?:FINAL|NEW|OLD)\s+TABLE\s*\(", piece):
|
|
325
359
|
# SELECT ... FROM FINAL TABLE (INSERT ...): the rows come from
|
|
326
360
|
# the data-change statement inside, not from a table named FINAL.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
361
|
+
skipped = "a data-change table reference in FROM is not traced"
|
|
362
|
+
elif re.match(r"TABLE\s*\(", piece):
|
|
363
|
+
skipped = "a table function in FROM is not traced"
|
|
364
|
+
elif re.match(r"LATERAL\s*\(", piece):
|
|
365
|
+
skipped = "a LATERAL sub-select in FROM is not traced"
|
|
366
|
+
elif re.match(r"(?:XMLTABLE|JSON_TABLE)\s*\(", piece):
|
|
367
|
+
skipped = "an XMLTABLE / JSON_TABLE item in FROM is not traced"
|
|
368
|
+
elif re.match(r"UNNEST\s*\(", piece):
|
|
369
|
+
skipped = "an UNNEST item in FROM is not traced"
|
|
370
|
+
elif re.match(r"\S*['\"(]", piece):
|
|
371
|
+
# Whatever the item is, a first word holding ``(`` or a quote
|
|
372
|
+
# is never a table name (a delimited ``"MY TAB"`` is not read).
|
|
373
|
+
skipped = "a FROM item that is not a table name is not traced"
|
|
374
|
+
if skipped is not None:
|
|
375
|
+
caveats.append(skipped)
|
|
376
|
+
name = _skipped_correlation(piece)
|
|
377
|
+
# Even with no correlation name the item's columns are in scope,
|
|
378
|
+
# so an unqualified column may come from it: "" marks that.
|
|
379
|
+
aliases[name or ""] = ""
|
|
331
380
|
continue
|
|
332
381
|
# ``BANK . ACCOUNT`` is one qualified name: join it before splitting
|
|
333
382
|
# the piece into table and correlation name.
|
|
@@ -349,11 +398,14 @@ def _resolve_column(
|
|
|
349
398
|
) -> list[tuple[str, str, bool]]:
|
|
350
399
|
if qualifier is not None:
|
|
351
400
|
table = aliases.get(qualifier)
|
|
352
|
-
if table
|
|
401
|
+
if table:
|
|
353
402
|
return [(table, column, True)]
|
|
354
|
-
|
|
403
|
+
# An untraced FROM item's correlation name (""), or a qualifier that
|
|
404
|
+
# names no FROM item: no table of that name is invented.
|
|
405
|
+
return []
|
|
406
|
+
untraced = "" in aliases.values()
|
|
355
407
|
if len(tables) == 1:
|
|
356
|
-
return [(tables[0], column,
|
|
408
|
+
return [(tables[0], column, not untraced)]
|
|
357
409
|
return [(table, column, False) for table in tables]
|
|
358
410
|
|
|
359
411
|
|
|
@@ -405,6 +457,11 @@ def _select_flows(
|
|
|
405
457
|
result.caveats.append("SELECT * INTO is not traced column by column")
|
|
406
458
|
continue
|
|
407
459
|
for qualifier, column in _columns_in(expr):
|
|
460
|
+
if qualifier is not None and qualifier not in aliases:
|
|
461
|
+
result.caveats.append(
|
|
462
|
+
f"{qualifier}.{column} names no table or correlation name "
|
|
463
|
+
"in FROM; not traced"
|
|
464
|
+
)
|
|
408
465
|
for table, col, exact in _resolve_column(
|
|
409
466
|
qualifier, column, aliases, tables
|
|
410
467
|
):
|
|
@@ -814,11 +814,20 @@ class BoundaryInventoryAnalyzer:
|
|
|
814
814
|
f"{category} {record_name}: record not resolvable in layout index"
|
|
815
815
|
)
|
|
816
816
|
report.unresolved.extend(gaps)
|
|
817
|
+
# The FILE STATUS of the FD the record belongs to: a WRITE or
|
|
818
|
+
# REWRITE sets it like any other operation on the file.
|
|
819
|
+
fd_file = record.get_metadata("fd_file_name") if record else None
|
|
820
|
+
write_facts = index.file_facts(str(fd_file)) if fd_file else None
|
|
817
821
|
report.boundaries.append(
|
|
818
822
|
Boundary(
|
|
819
823
|
ref=ref(category, record_name.upper()),
|
|
820
824
|
direction="OUT",
|
|
821
825
|
record_names=record_names,
|
|
826
|
+
status_field=(
|
|
827
|
+
write_facts.file_status_field
|
|
828
|
+
if write_facts is not None
|
|
829
|
+
else None
|
|
830
|
+
),
|
|
822
831
|
handlers=handlers,
|
|
823
832
|
unresolved=gaps,
|
|
824
833
|
)
|
|
@@ -222,6 +222,13 @@ _RELATIONAL_TEXT_OP_CANON: dict[str, str] = {
|
|
|
222
222
|
"<": "lt",
|
|
223
223
|
}
|
|
224
224
|
_BARE_IDENTIFIER_RE = re.compile(r"^[A-Za-z][A-Za-z0-9-]*$")
|
|
225
|
+
# A class or sign test in relational text: ``LK-ID IS NOT NUMERIC``.
|
|
226
|
+
_CLASS_TEXT_RE = re.compile(
|
|
227
|
+
r"^([A-Za-z][A-Za-z0-9-]*)\s+(?:IS\s+)?(NOT\s+)?"
|
|
228
|
+
r"(NUMERIC|ALPHABETIC(?:\s*-\s*(?:UPPER|LOWER))?|POSITIVE|NEGATIVE"
|
|
229
|
+
r"|ZEROES|ZEROS|ZERO)$",
|
|
230
|
+
re.IGNORECASE,
|
|
231
|
+
)
|
|
225
232
|
|
|
226
233
|
|
|
227
234
|
@dataclass(frozen=True)
|
|
@@ -427,12 +434,61 @@ class TransferSite:
|
|
|
427
434
|
}
|
|
428
435
|
|
|
429
436
|
|
|
437
|
+
#: A file statement's handler clauses that nest another statement: the
|
|
438
|
+
#: nested one runs only when the outer statement takes that clause.
|
|
439
|
+
_HANDLER_CLAUSES = (
|
|
440
|
+
"invalid_key_clause",
|
|
441
|
+
"not_invalid_key_clause",
|
|
442
|
+
"at_end_clause",
|
|
443
|
+
"not_at_end_clause",
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@dataclass(frozen=True)
|
|
448
|
+
class StatementSite:
|
|
449
|
+
"""Where one boundary statement sits (keyed by its line in
|
|
450
|
+
`ConstraintReport.statement_sites`): its paragraph, the branch arms
|
|
451
|
+
enclosing it there (`BranchConstraints.enclosing`'s shape), and the
|
|
452
|
+
handler clauses of other statements it is nested in, outermost first,
|
|
453
|
+
as ``(outer statement line, clause)`` with the clause one of
|
|
454
|
+
``INVALID_KEY``, ``NOT_INVALID_KEY``, ``AT_END``, ``NOT_AT_END``.
|
|
455
|
+
|
|
456
|
+
The planner's status families read it to pin the path to the statement
|
|
457
|
+
they fail: a READ that runs only for one transaction code, or a WRITE
|
|
458
|
+
that runs only in another READ's INVALID KEY clause."""
|
|
459
|
+
|
|
460
|
+
paragraph: str
|
|
461
|
+
enclosing: tuple[tuple[str, str], ...] = ()
|
|
462
|
+
handlers: tuple[tuple[int, str], ...] = ()
|
|
463
|
+
#: A READ that can return FILE STATUS "02" (another record has the same
|
|
464
|
+
#: alternate key): one by ``KEY IS`` an ALTERNATE RECORD KEY WITH
|
|
465
|
+
#: DUPLICATES. A READ by the prime key never can; a sequential one can
|
|
466
|
+
#: when its key of reference is such a key, which is not tracked.
|
|
467
|
+
duplicate_key: bool = False
|
|
468
|
+
|
|
469
|
+
def to_dict(self) -> dict[str, Any]:
|
|
470
|
+
return {
|
|
471
|
+
"paragraph": self.paragraph,
|
|
472
|
+
"enclosing": [list(pair) for pair in self.enclosing],
|
|
473
|
+
"handlers": [list(pair) for pair in self.handlers],
|
|
474
|
+
"duplicate_key": self.duplicate_key,
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
|
|
430
478
|
@dataclass
|
|
431
479
|
class ConstraintReport:
|
|
432
480
|
branches: list[BranchConstraints] = field(default_factory=list)
|
|
433
481
|
sequence_constraints: list[SequenceConstraint] = field(default_factory=list)
|
|
434
482
|
unsupported: list[str] = field(default_factory=list)
|
|
435
483
|
transfer_sites: list[TransferSite] = field(default_factory=list)
|
|
484
|
+
#: Uppercased data-name -> the two-character codes the program tests it
|
|
485
|
+
#: against (``IF WS-FS = '23'``, an 88 under it, ``EVALUATE WS-FS WHEN
|
|
486
|
+
#: '02'``), sorted. Read for FILE STATUS fields by the planner's
|
|
487
|
+
#: file-status family; never a branch constraint (a status field is
|
|
488
|
+
#: set by the file operation, not by data).
|
|
489
|
+
status_tests: dict[str, list[str]] = field(default_factory=dict)
|
|
490
|
+
#: Boundary statement line -> its `StatementSite`.
|
|
491
|
+
statement_sites: dict[int, StatementSite] = field(default_factory=dict)
|
|
436
492
|
|
|
437
493
|
def to_dict(self) -> dict[str, Any]:
|
|
438
494
|
return {
|
|
@@ -440,6 +496,11 @@ class ConstraintReport:
|
|
|
440
496
|
"sequence_constraints": [s.to_dict() for s in self.sequence_constraints],
|
|
441
497
|
"unsupported": list(self.unsupported),
|
|
442
498
|
"transfer_sites": [t.to_dict() for t in self.transfer_sites],
|
|
499
|
+
"status_tests": {k: list(v) for k, v in self.status_tests.items()},
|
|
500
|
+
"statement_sites": {
|
|
501
|
+
str(line): site.to_dict()
|
|
502
|
+
for line, site in sorted(self.statement_sites.items())
|
|
503
|
+
},
|
|
443
504
|
}
|
|
444
505
|
|
|
445
506
|
|
|
@@ -473,6 +534,9 @@ class ConstraintExtractor:
|
|
|
473
534
|
|
|
474
535
|
fed_names = _boundary_fed_names(boundary_report)
|
|
475
536
|
fed_names.update(name.upper() for name in extra_fed_names)
|
|
537
|
+
index.named_constants = _named_constants(ast, index)
|
|
538
|
+
report.status_tests = _status_tests(ast, procedure, index)
|
|
539
|
+
report.statement_sites = _statement_sites(procedure, boundary_report, index)
|
|
476
540
|
eof_flags = _collect_eof_flags(ast)
|
|
477
541
|
carriers = _Carriers(ast, procedure, index, fed_names)
|
|
478
542
|
|
|
@@ -1022,8 +1086,22 @@ def _extract_class_or_sign(
|
|
|
1022
1086
|
origin: str,
|
|
1023
1087
|
unsupported: list[str],
|
|
1024
1088
|
) -> FieldConstraint | None:
|
|
1025
|
-
effective_negate = negate != is_not
|
|
1026
1089
|
name = expr.left_operand if isinstance(expr.left_operand, str) else ""
|
|
1090
|
+
return _class_or_sign_constraint(
|
|
1091
|
+
name, class_word, negate != is_not, index, fed_names, origin, unsupported
|
|
1092
|
+
)
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
def _class_or_sign_constraint(
|
|
1096
|
+
name: str,
|
|
1097
|
+
class_word: str,
|
|
1098
|
+
effective_negate: bool,
|
|
1099
|
+
index: ProgramLayoutIndex,
|
|
1100
|
+
fed_names: set[str],
|
|
1101
|
+
origin: str,
|
|
1102
|
+
unsupported: list[str],
|
|
1103
|
+
) -> FieldConstraint | None:
|
|
1104
|
+
"""``name [NOT] <class or sign word>`` as one constraint, or None."""
|
|
1027
1105
|
if not name.strip():
|
|
1028
1106
|
unsupported.append(f"{origin}: class/sign condition missing operand")
|
|
1029
1107
|
return None
|
|
@@ -1302,8 +1380,18 @@ def _build_relational_constraint(
|
|
|
1302
1380
|
if not left_text:
|
|
1303
1381
|
unsupported.append(f"{origin}: comparison missing left operand")
|
|
1304
1382
|
return None
|
|
1383
|
+
left_text = _fold_cics_value(left_text)
|
|
1384
|
+
right_text = _fold_cics_value(right_text) if right_text else right_text
|
|
1305
1385
|
left_field = index.resolve(left_text)
|
|
1306
1386
|
right_field = index.resolve(right_text) if right_text else None
|
|
1387
|
+
if left_field is not None and right_field is not None:
|
|
1388
|
+
# A WORKING-STORAGE item the program never changes is its VALUE.
|
|
1389
|
+
right_constant = _constant_of(right_field, index)
|
|
1390
|
+
left_constant = _constant_of(left_field, index)
|
|
1391
|
+
if right_constant is not None and left_constant is None:
|
|
1392
|
+
right_text, right_field = right_constant, None
|
|
1393
|
+
elif left_constant is not None and right_constant is None:
|
|
1394
|
+
left_text, left_field = left_constant, None
|
|
1307
1395
|
|
|
1308
1396
|
if left_field is not None and right_field is not None:
|
|
1309
1397
|
if site is not None:
|
|
@@ -1559,6 +1647,21 @@ def _parse_relational_text(
|
|
|
1559
1647
|
quoted_operands=False,
|
|
1560
1648
|
)
|
|
1561
1649
|
|
|
1650
|
+
class_match = _CLASS_TEXT_RE.match(text)
|
|
1651
|
+
if class_match:
|
|
1652
|
+
# ``WHEN LK-ID NOT NUMERIC`` / ``WHEN WS-AMT IS POSITIVE``: the class
|
|
1653
|
+
# and sign tests the IF path models from the condition tree.
|
|
1654
|
+
name, is_not, class_word = class_match.groups()
|
|
1655
|
+
return _class_or_sign_constraint(
|
|
1656
|
+
name,
|
|
1657
|
+
re.sub(r"\s+", "", class_word.upper()),
|
|
1658
|
+
bool(is_not),
|
|
1659
|
+
index,
|
|
1660
|
+
fed_names,
|
|
1661
|
+
origin,
|
|
1662
|
+
unsupported,
|
|
1663
|
+
)
|
|
1664
|
+
|
|
1562
1665
|
if _BARE_IDENTIFIER_RE.match(text):
|
|
1563
1666
|
return _extract_condition_name_value(
|
|
1564
1667
|
text, False, index, fed_names, origin, unsupported
|
|
@@ -1890,6 +1993,174 @@ def _holds_losslessly(carrier: Any, source: Any) -> bool:
|
|
|
1890
1993
|
return False
|
|
1891
1994
|
|
|
1892
1995
|
|
|
1996
|
+
#: ``DFHRESP(condition)`` values (the EIBRESP code the CICS translator
|
|
1997
|
+
#: substitutes), for a comparison of a RESP field against one.
|
|
1998
|
+
_DFHRESP_VALUES: dict[str, int] = {
|
|
1999
|
+
"NORMAL": 0,
|
|
2000
|
+
"ERROR": 1,
|
|
2001
|
+
"RDATT": 2,
|
|
2002
|
+
"WRBRK": 3,
|
|
2003
|
+
"EOF": 4,
|
|
2004
|
+
"EODS": 5,
|
|
2005
|
+
"EOC": 6,
|
|
2006
|
+
"INBFMH": 7,
|
|
2007
|
+
"ENDINPT": 8,
|
|
2008
|
+
"NONVAL": 9,
|
|
2009
|
+
"NOSTART": 10,
|
|
2010
|
+
"TERMIDERR": 11,
|
|
2011
|
+
"FILENOTFOUND": 12,
|
|
2012
|
+
"DSIDERR": 12,
|
|
2013
|
+
"NOTFND": 13,
|
|
2014
|
+
"DUPREC": 14,
|
|
2015
|
+
"DUPKEY": 15,
|
|
2016
|
+
"INVREQ": 16,
|
|
2017
|
+
"IOERR": 17,
|
|
2018
|
+
"NOSPACE": 18,
|
|
2019
|
+
"NOTOPEN": 19,
|
|
2020
|
+
"ENDFILE": 20,
|
|
2021
|
+
"ILLOGIC": 21,
|
|
2022
|
+
"LENGERR": 22,
|
|
2023
|
+
"QZERO": 23,
|
|
2024
|
+
"SIGNAL": 24,
|
|
2025
|
+
"QBUSY": 25,
|
|
2026
|
+
"ITEMERR": 26,
|
|
2027
|
+
"PGMIDERR": 27,
|
|
2028
|
+
"TRANSIDERR": 28,
|
|
2029
|
+
"ENDDATA": 29,
|
|
2030
|
+
"EXPIRED": 31,
|
|
2031
|
+
"MAPFAIL": 36,
|
|
2032
|
+
"INVMPSZ": 38,
|
|
2033
|
+
"QIDERR": 44,
|
|
2034
|
+
"SYSIDERR": 53,
|
|
2035
|
+
"NOTAUTH": 70,
|
|
2036
|
+
"DISABLED": 84,
|
|
2037
|
+
}
|
|
2038
|
+
_DFHRESP_RE = re.compile(r"^DFHRESP\s*\(\s*([A-Z0-9]+)\s*\)$", re.IGNORECASE)
|
|
2039
|
+
|
|
2040
|
+
|
|
2041
|
+
def _fold_cics_value(text: str) -> str:
|
|
2042
|
+
"""``DFHRESP(NOTFND)`` as the number the CICS translator puts there."""
|
|
2043
|
+
match = _DFHRESP_RE.match(text.strip())
|
|
2044
|
+
if match is None:
|
|
2045
|
+
return text
|
|
2046
|
+
value = _DFHRESP_VALUES.get(match.group(1).upper())
|
|
2047
|
+
return text if value is None else str(value)
|
|
2048
|
+
|
|
2049
|
+
|
|
2050
|
+
#: xref roles that never change the referenced item.
|
|
2051
|
+
_READ_ONLY_ROLES = frozenset({"read", "condition", "subscript"})
|
|
2052
|
+
|
|
2053
|
+
|
|
2054
|
+
_TWO_CHAR_LITERAL_RE = re.compile(r"""^(['"])(.{2})\1$""")
|
|
2055
|
+
|
|
2056
|
+
|
|
2057
|
+
def _status_tests(
|
|
2058
|
+
ast: CobolProgram, procedure: Any, index: ProgramLayoutIndex
|
|
2059
|
+
) -> dict[str, list[str]]:
|
|
2060
|
+
"""Every two-character literal each data item is compared with, and
|
|
2061
|
+
the VALUEs of each condition-name tested, by the item's uppercased
|
|
2062
|
+
leaf name (`ConstraintReport.status_tests`). Collected from IF and
|
|
2063
|
+
PERFORM UNTIL comparisons and from EVALUATE subjects and selectors."""
|
|
2064
|
+
found: dict[str, set[str]] = {}
|
|
2065
|
+
|
|
2066
|
+
def leaf(name: str) -> str | None:
|
|
2067
|
+
resolved = index.resolve(name.strip())
|
|
2068
|
+
if resolved is None:
|
|
2069
|
+
return None
|
|
2070
|
+
return resolved.spec.qualified_name.upper().rsplit(".", 1)[-1]
|
|
2071
|
+
|
|
2072
|
+
def add_literal(name: str, literal: object) -> None:
|
|
2073
|
+
match = _TWO_CHAR_LITERAL_RE.match(str(literal or "").strip())
|
|
2074
|
+
target = leaf(name) if name else None
|
|
2075
|
+
if match and target:
|
|
2076
|
+
found.setdefault(target, set()).add(match.group(2))
|
|
2077
|
+
|
|
2078
|
+
def add_condition_name(name: str) -> None:
|
|
2079
|
+
values = index.condition_values(name.strip())
|
|
2080
|
+
target = leaf(name)
|
|
2081
|
+
if values and target:
|
|
2082
|
+
found.setdefault(target, set()).update(
|
|
2083
|
+
str(v) for v in values if len(str(v)) == 2
|
|
2084
|
+
)
|
|
2085
|
+
|
|
2086
|
+
def add_text(text: str) -> None:
|
|
2087
|
+
match = _RELATIONAL_TEXT_RE.match(text.strip())
|
|
2088
|
+
if match:
|
|
2089
|
+
add_literal(match.group(1), match.group(3).strip())
|
|
2090
|
+
elif _BARE_IDENTIFIER_RE.match(text.strip()):
|
|
2091
|
+
add_condition_name(text)
|
|
2092
|
+
|
|
2093
|
+
for expr in ast.find_all_by_type("ConditionalExpression"):
|
|
2094
|
+
if not isinstance(expr, ConditionalExpression):
|
|
2095
|
+
continue
|
|
2096
|
+
left = expr.left_operand if isinstance(expr.left_operand, str) else ""
|
|
2097
|
+
if expr.operator == "CONDITION_NAME" and left:
|
|
2098
|
+
add_condition_name(left)
|
|
2099
|
+
elif left:
|
|
2100
|
+
add_literal(left, expr.right_operand)
|
|
2101
|
+
for _, statement, _ in _walk_statements(procedure):
|
|
2102
|
+
if not isinstance(statement, EvaluateStatement):
|
|
2103
|
+
continue
|
|
2104
|
+
subjects = statement.subjects
|
|
2105
|
+
subject = subjects[0] if len(subjects) == 1 else None
|
|
2106
|
+
is_true = isinstance(subject, str) and subject.strip().upper() == "TRUE"
|
|
2107
|
+
for when in statement.when_clauses:
|
|
2108
|
+
for selector in when.selectors:
|
|
2109
|
+
if not isinstance(selector, dict):
|
|
2110
|
+
continue
|
|
2111
|
+
if is_true and selector.get("type") == "Literal":
|
|
2112
|
+
add_condition_name(str(selector.get("value", "")))
|
|
2113
|
+
elif is_true and selector.get("type") == "Condition":
|
|
2114
|
+
add_text(str(selector.get("text", "")))
|
|
2115
|
+
elif isinstance(subject, str) and selector.get("type") == "Literal":
|
|
2116
|
+
value = str(selector.get("value", ""))
|
|
2117
|
+
if selector.get("quoted"):
|
|
2118
|
+
value = f"'{value}'"
|
|
2119
|
+
add_literal(subject, value)
|
|
2120
|
+
return {name: sorted(codes) for name, codes in found.items()}
|
|
2121
|
+
|
|
2122
|
+
|
|
2123
|
+
def _named_constants(ast: CobolProgram, index: ProgramLayoutIndex) -> dict[str, str]:
|
|
2124
|
+
"""``{qualified name: VALUE literal}`` of every WORKING-STORAGE
|
|
2125
|
+
elementary item with a literal VALUE that nothing in the program writes,
|
|
2126
|
+
neither the item nor a group above it (a MOVE to the group, a READ INTO
|
|
2127
|
+
it, an INITIALIZE, a CALL that may change it all count as writes)."""
|
|
2128
|
+
from .xref import XrefAnalyzer
|
|
2129
|
+
|
|
2130
|
+
try:
|
|
2131
|
+
entries = XrefAnalyzer().analyze(ast).entries
|
|
2132
|
+
except Exception: # noqa: BLE001 - best effort: no constants
|
|
2133
|
+
return {}
|
|
2134
|
+
changed = {
|
|
2135
|
+
entry.name.upper()
|
|
2136
|
+
for entry in entries
|
|
2137
|
+
if any(ref.role not in _READ_ONLY_ROLES for ref in entry.references)
|
|
2138
|
+
}
|
|
2139
|
+
constants: dict[str, str] = {}
|
|
2140
|
+
for entry in entries:
|
|
2141
|
+
if entry.section != "WORKING-STORAGE" or entry.level in (None, 66, 88):
|
|
2142
|
+
continue
|
|
2143
|
+
resolved = index.resolve(entry.name)
|
|
2144
|
+
if resolved is None or resolved.section != "working_storage":
|
|
2145
|
+
continue
|
|
2146
|
+
spec = resolved.spec
|
|
2147
|
+
if spec.redefines or spec.subscripts or not spec.value_clause:
|
|
2148
|
+
continue
|
|
2149
|
+
if any(part.upper() in changed for part in (*spec.path, spec.name)):
|
|
2150
|
+
continue
|
|
2151
|
+
if resolved.record.name.upper() in changed:
|
|
2152
|
+
continue
|
|
2153
|
+
value = str(spec.value_clause).strip()
|
|
2154
|
+
if not _is_literal_operand(value) or _figurative_name(value) is not None:
|
|
2155
|
+
continue
|
|
2156
|
+
constants[spec.qualified_name.upper()] = value
|
|
2157
|
+
return constants
|
|
2158
|
+
|
|
2159
|
+
|
|
2160
|
+
def _constant_of(resolved: ResolvedField, index: ProgramLayoutIndex) -> str | None:
|
|
2161
|
+
return index.named_constants.get(resolved.spec.qualified_name.upper())
|
|
2162
|
+
|
|
2163
|
+
|
|
1893
2164
|
def _boundary_fed_names(report: BoundaryReport) -> set[str]:
|
|
1894
2165
|
fed: set[str] = set()
|
|
1895
2166
|
for boundary in report.boundaries:
|
|
@@ -2151,6 +2422,79 @@ def _record_transfer_site(
|
|
|
2151
2422
|
)
|
|
2152
2423
|
|
|
2153
2424
|
|
|
2425
|
+
def _duplicate_key_read(statement: Any, index: ProgramLayoutIndex) -> bool:
|
|
2426
|
+
if not isinstance(statement, IOReadStatement) or not statement.file_name:
|
|
2427
|
+
return False
|
|
2428
|
+
facts = index.file_facts(statement.file_name)
|
|
2429
|
+
if facts is None:
|
|
2430
|
+
return False
|
|
2431
|
+
duplicates = {
|
|
2432
|
+
str(key.get("key", "")).upper()
|
|
2433
|
+
for key in facts.alternate_keys
|
|
2434
|
+
if key.get("duplicates_allowed")
|
|
2435
|
+
}
|
|
2436
|
+
# A sequential READ returns "02" only when the key of reference (set by
|
|
2437
|
+
# an earlier START / READ ... KEY) is such a key, which is not tracked:
|
|
2438
|
+
# left out rather than guessed.
|
|
2439
|
+
return bool(statement.key_name) and statement.key_name.upper() in duplicates
|
|
2440
|
+
|
|
2441
|
+
|
|
2442
|
+
def _statement_sites(
|
|
2443
|
+
procedure_division: CobolDivision,
|
|
2444
|
+
boundary_report: BoundaryReport,
|
|
2445
|
+
index: ProgramLayoutIndex,
|
|
2446
|
+
) -> dict[int, StatementSite]:
|
|
2447
|
+
"""`StatementSite` of every boundary statement, by line (the first
|
|
2448
|
+
statement on a line wins)."""
|
|
2449
|
+
lines = {b.ref.line for b in boundary_report.boundaries if b.ref.line}
|
|
2450
|
+
sites: dict[int, StatementSite] = {}
|
|
2451
|
+
if not lines:
|
|
2452
|
+
return sites
|
|
2453
|
+
|
|
2454
|
+
def walk(
|
|
2455
|
+
paragraph: str,
|
|
2456
|
+
statements: list[Any],
|
|
2457
|
+
enclosing: tuple[tuple[str, str], ...],
|
|
2458
|
+
handlers: tuple[tuple[int, str], ...],
|
|
2459
|
+
) -> None:
|
|
2460
|
+
for statement in statements:
|
|
2461
|
+
location = getattr(statement, "location", None)
|
|
2462
|
+
line = location.line if location is not None else None
|
|
2463
|
+
if line in lines and line not in sites:
|
|
2464
|
+
sites[line] = StatementSite(
|
|
2465
|
+
paragraph,
|
|
2466
|
+
enclosing,
|
|
2467
|
+
handlers,
|
|
2468
|
+
_duplicate_key_read(statement, index),
|
|
2469
|
+
)
|
|
2470
|
+
is_branch = isinstance(
|
|
2471
|
+
statement, (IfStatement, EvaluateStatement, SearchStatement)
|
|
2472
|
+
)
|
|
2473
|
+
branch_id = _branch_id(paragraph, location) if is_branch else None
|
|
2474
|
+
clause_lists = {
|
|
2475
|
+
id(getattr(statement, attr, None)): attr.removesuffix("_clause").upper()
|
|
2476
|
+
for attr in _HANDLER_CLAUSES
|
|
2477
|
+
if getattr(statement, attr, None)
|
|
2478
|
+
}
|
|
2479
|
+
for arm_id, nested in _nested_statement_lists(statement):
|
|
2480
|
+
deeper = (
|
|
2481
|
+
enclosing + ((branch_id, arm_id),)
|
|
2482
|
+
if arm_id is not None and branch_id is not None
|
|
2483
|
+
else enclosing
|
|
2484
|
+
)
|
|
2485
|
+
clause = clause_lists.get(id(nested))
|
|
2486
|
+
outer = (
|
|
2487
|
+
handlers + ((line, clause),)
|
|
2488
|
+
if clause is not None and line is not None and not is_branch
|
|
2489
|
+
else handlers
|
|
2490
|
+
)
|
|
2491
|
+
walk(paragraph, nested, deeper, outer)
|
|
2492
|
+
|
|
2493
|
+
for paragraph in _all_paragraphs(procedure_division):
|
|
2494
|
+
walk(paragraph.paragraph_name, paragraph.statements, (), ())
|
|
2495
|
+
return sites
|
|
2496
|
+
|
|
2497
|
+
|
|
2154
2498
|
def _branch_id(paragraph_name: str | None, location: SourceLocation | None) -> str:
|
|
2155
2499
|
line = location.line if location else "?"
|
|
2156
2500
|
column = location.column if location else "?"
|
|
@@ -2164,5 +2508,6 @@ __all__ = [
|
|
|
2164
2508
|
"BranchConstraints",
|
|
2165
2509
|
"ConstraintReport",
|
|
2166
2510
|
"TransferSite",
|
|
2511
|
+
"StatementSite",
|
|
2167
2512
|
"ConstraintExtractor",
|
|
2168
2513
|
]
|