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.
Files changed (233) hide show
  1. {cobol_parser-3.2.1/cobol_parser.egg-info → cobol_parser-3.3.0}/PKG-INFO +4 -4
  2. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/README.md +2 -2
  3. {cobol_parser-3.2.1 → cobol_parser-3.3.0/cobol_parser.egg-info}/PKG-INFO +4 -4
  4. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/SOURCES.txt +6 -0
  5. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/requires.txt +1 -1
  6. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/__init__.py +1 -1
  7. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_sql_flow.py +68 -11
  8. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/boundary_inventory.py +9 -0
  9. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/constraints.py +346 -1
  10. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/value_tracker.py +321 -4
  11. cobol_parser-3.3.0/cobolparser/definitions/__init__.py +134 -0
  12. cobol_parser-3.3.0/cobolparser/definitions/_asm.py +269 -0
  13. cobol_parser-3.3.0/cobolparser/definitions/bms.py +269 -0
  14. cobol_parser-3.3.0/cobolparser/definitions/csd.py +484 -0
  15. cobol_parser-3.3.0/cobolparser/definitions/ddl.py +759 -0
  16. cobol_parser-3.3.0/cobolparser/definitions/ims.py +407 -0
  17. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/program_layout.py +84 -0
  18. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/program_fixtures.py +64 -11
  19. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/scenarios.py +274 -7
  20. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/claims.py +297 -20
  21. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/jcl_tools.py +7 -1
  22. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/link_decks.py +66 -34
  23. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/slicing.py +192 -91
  24. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/sort_layout.py +148 -16
  25. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/ibm_exec_statements.py +9 -1
  26. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/pyproject.toml +2 -2
  27. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/LICENSE +0 -0
  28. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/MANIFEST.in +0 -0
  29. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/dependency_links.txt +0 -0
  30. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/entry_points.txt +0 -0
  31. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobol_parser.egg-info/top_level.txt +0 -0
  32. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/__main__.py +0 -0
  33. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_accel.py +0 -0
  34. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_data_fast.c +0 -0
  35. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_data_fast.pyx +0 -0
  36. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_errors.py +0 -0
  37. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_facts_cache.py +0 -0
  38. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_lexer_fast.c +0 -0
  39. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_lexer_fast.pyx +0 -0
  40. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_lines.py +0 -0
  41. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_literals.py +0 -0
  42. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_preprocess_fast.c +0 -0
  43. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_preprocess_fast.pyx +0 -0
  44. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_source_directive.py +0 -0
  45. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/_storage.py +0 -0
  46. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/__init__.py +0 -0
  47. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_cics.py +0 -0
  48. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_files.py +0 -0
  49. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_names.py +0 -0
  50. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_procedures.py +0 -0
  51. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/_sql_whenever.py +0 -0
  52. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/ast_diff.py +0 -0
  53. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/business_rules.py +0 -0
  54. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/call_interface.py +0 -0
  55. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/codebase.py +0 -0
  56. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/complexity.py +0 -0
  57. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/const_fold.py +0 -0
  58. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/crud_matrix.py +0 -0
  59. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/dclgen_check.py +0 -0
  60. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/dead_code.py +0 -0
  61. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/domains.py +0 -0
  62. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/field_lineage.py +0 -0
  63. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/field_widening.py +0 -0
  64. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/host_var_resolver.py +0 -0
  65. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/ims_segment_analyzer.py +0 -0
  66. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/invocation_counts.py +0 -0
  67. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/io_coverage_reporter.py +0 -0
  68. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/io_diagnostics.py +0 -0
  69. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/io_logger.py +0 -0
  70. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/io_sequence.py +0 -0
  71. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/language_env_analyzer.py +0 -0
  72. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/lineage_graph.py +0 -0
  73. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/lineage_jcl.py +0 -0
  74. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/linkage_checker.py +0 -0
  75. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/lint.py +0 -0
  76. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/move_type_checker.py +0 -0
  77. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/__init__.py +0 -0
  78. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/analyzer.py +0 -0
  79. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/condition_text.py +0 -0
  80. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/cycles.py +0 -0
  81. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/edges.py +0 -0
  82. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/graph.py +0 -0
  83. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/models.py +0 -0
  84. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/scanner.py +0 -0
  85. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_flow/statement_text.py +0 -0
  86. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/program_signature.py +0 -0
  87. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/recursion_risk.py +0 -0
  88. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/redundancy.py +0 -0
  89. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/sarif.py +0 -0
  90. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/sql_cics_lint.py +0 -0
  91. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/technical_debt.py +0 -0
  92. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/unused_variables.py +0 -0
  93. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/analysis/xref.py +0 -0
  94. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/batch.py +0 -0
  95. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/cli/__init__.py +0 -0
  96. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/cli/_project_config.py +0 -0
  97. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/cli/coverage.py +0 -0
  98. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/cli/parse.py +0 -0
  99. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/config/__init__.py +0 -0
  100. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/config/_yaml.py +0 -0
  101. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/config/project.py +0 -0
  102. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/__init__.py +0 -0
  103. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/ast_factory.py +0 -0
  104. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/exceptions.py +0 -0
  105. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/interfaces.py +0 -0
  106. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/registry.py +0 -0
  107. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/core/token_stream.py +0 -0
  108. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/dialects/__init__.py +0 -0
  109. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/dialects/ibm_enterprise.py +0 -0
  110. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/dialects/le_signatures.py +0 -0
  111. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/__init__.py +0 -0
  112. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/copybook_data.py +0 -0
  113. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/__init__.py +0 -0
  114. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/bundle.py +0 -0
  115. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/compare.py +0 -0
  116. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/copybook.py +0 -0
  117. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/dataset.py +0 -0
  118. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/decoding.py +0 -0
  119. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/editing.py +0 -0
  120. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/encoding.py +0 -0
  121. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/errors.py +0 -0
  122. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/layout.py +0 -0
  123. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/layout_offsets.py +0 -0
  124. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/options.py +0 -0
  125. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/storage.py +0 -0
  126. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/values.py +0 -0
  127. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/data/views.py +0 -0
  128. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/flowchart.py +0 -0
  129. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/generators/verification.py +0 -0
  130. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/__init__.py +0 -0
  131. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/bindings.py +0 -0
  132. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/models.py +0 -0
  133. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/parser.py +0 -0
  134. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/jcl/programs.py +0 -0
  135. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/lexer.py +0 -0
  136. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/__init__.py +0 -0
  137. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/codebase_index.py +0 -0
  138. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/envelope.py +0 -0
  139. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/facts.py +0 -0
  140. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/hints.py +0 -0
  141. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/languages.py +0 -0
  142. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/navigation.py +0 -0
  143. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/prompts.py +0 -0
  144. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/server.py +0 -0
  145. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/tools.py +0 -0
  146. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/mcp/workspace.py +0 -0
  147. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/__init__.py +0 -0
  148. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/ast_base.py +0 -0
  149. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/data_items.py +0 -0
  150. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/program.py +0 -0
  151. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/sql_statements.py +0 -0
  152. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/__init__.py +0 -0
  153. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/arithmetic.py +0 -0
  154. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/base.py +0 -0
  155. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/call.py +0 -0
  156. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/communication.py +0 -0
  157. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/control.py +0 -0
  158. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/data.py +0 -0
  159. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/io.py +0 -0
  160. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/legacy.py +0 -0
  161. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/misc.py +0 -0
  162. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/models/statements/report_writer.py +0 -0
  163. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parser.py +0 -0
  164. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/__init__.py +0 -0
  165. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/__init__.py +0 -0
  166. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/base.py +0 -0
  167. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data.py +0 -0
  168. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data_clauses.py +0 -0
  169. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data_file_description.py +0 -0
  170. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data_report_description.py +0 -0
  171. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/data_special_entries.py +0 -0
  172. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/environment.py +0 -0
  173. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/environment_sections.py +0 -0
  174. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/environment_select.py +0 -0
  175. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/factory.py +0 -0
  176. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/identification.py +0 -0
  177. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/procedure.py +0 -0
  178. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/procedure_boundaries.py +0 -0
  179. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/procedure_recovery.py +0 -0
  180. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/divisions/procedure_registry.py +0 -0
  181. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/__init__.py +0 -0
  182. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/_conditions.py +0 -0
  183. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/_utils.py +0 -0
  184. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/accept_parser.py +0 -0
  185. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/allocate_free_parsers.py +0 -0
  186. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/arithmetic_parsers.py +0 -0
  187. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/call_parser.py +0 -0
  188. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/close_parser.py +0 -0
  189. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/communication_parsers.py +0 -0
  190. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/delete_parser.py +0 -0
  191. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/display_parser.py +0 -0
  192. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/entry_parser.py +0 -0
  193. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/evaluate_parser.py +0 -0
  194. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/exception_parsers.py +0 -0
  195. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/go_parser.py +0 -0
  196. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/if_parser.py +0 -0
  197. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/initialize_inspect_parsers.py +0 -0
  198. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/invoke_parser.py +0 -0
  199. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/legacy_verb_parsers.py +0 -0
  200. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/move_parser.py +0 -0
  201. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/open_parser.py +0 -0
  202. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/perform_parser.py +0 -0
  203. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/read_parser.py +0 -0
  204. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/report_writer_parsers.py +0 -0
  205. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/rewrite_parser.py +0 -0
  206. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/search_parser.py +0 -0
  207. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/set_parser.py +0 -0
  208. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/simple_statement_parsers.py +0 -0
  209. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/sort_merge_parsers.py +0 -0
  210. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/start_parser.py +0 -0
  211. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/stop_parser.py +0 -0
  212. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/string_unstring_parsers.py +0 -0
  213. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/unlock_parser.py +0 -0
  214. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/write_parser.py +0 -0
  215. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/parsers/statements/xml_json_parser.py +0 -0
  216. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/plugins/__init__.py +0 -0
  217. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/plugins/core.py +0 -0
  218. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/__init__.py +0 -0
  219. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/_normalize.py +0 -0
  220. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/copy_statement_parser.py +0 -0
  221. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/copybook_expander.py +0 -0
  222. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/models.py +0 -0
  223. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/path_resolver.py +0 -0
  224. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/replace_statement.py +0 -0
  225. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/preprocess/source_format.py +0 -0
  226. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/profiling.py +0 -0
  227. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/py.typed +0 -0
  228. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/cobolparser/scan.py +0 -0
  229. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/setup.cfg +0 -0
  230. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/setup.py +0 -0
  231. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/tests/test_all_samples.py +0 -0
  232. {cobol_parser-3.2.1 → cobol_parser-3.3.0}/tests/test_okf_claims.py +0 -0
  233. {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.2.1
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: hlasmparser>=0.2.0; extra == "hlasm"
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 is currently switched off (`.github/workflows/ci.yml` is commented out), so
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 is currently switched off (`.github/workflows/ci.yml` is commented out), so
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.2.1
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: hlasmparser>=0.2.0; extra == "hlasm"
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 is currently switched off (`.github/workflows/ci.yml` is commented out), so
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
@@ -18,7 +18,7 @@ sphinx-rtd-theme>=1.3.0
18
18
  cython>=3.0
19
19
 
20
20
  [hlasm]
21
- hlasmparser>=0.2.0
21
+ hlasm-parser>=0.2.2
22
22
 
23
23
  [jcl]
24
24
  jcl-parser>=2.0.0
@@ -231,7 +231,7 @@ from .scan import (
231
231
  CobolParseError = ParseError
232
232
 
233
233
  # Version info
234
- __version__ = "3.2.1" # x-release-please-version
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
- caveats.append("a sub-select in FROM is not traced")
323
- continue
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
- caveats.append("a data-change table reference in FROM is not traced")
328
- continue
329
- if re.match(r"TABLE\s*\(", piece):
330
- caveats.append("a table function in FROM is not traced")
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 is not None:
401
+ if table:
353
402
  return [(table, column, True)]
354
- return [(qualifier, column, False)]
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, True)]
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
  ]