simdref 0.0.20__tar.gz → 0.0.21__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 (209) hide show
  1. {simdref-0.0.20/src/simdref.egg-info → simdref-0.0.21}/PKG-INFO +1 -1
  2. {simdref-0.0.20 → simdref-0.0.21}/pyproject.toml +1 -1
  3. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/cuda.py +98 -7
  4. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/cuda_docs.py +213 -35
  5. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/display.py +281 -49
  6. simdref-0.0.21/src/simdref/docblocks.py +803 -0
  7. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/export.py +14 -2
  8. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ingest_catalog.py +2 -0
  9. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/models.py +4 -0
  10. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ptx.py +60 -14
  11. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/tui.py +52 -34
  12. {simdref-0.0.20 → simdref-0.0.21/src/simdref.egg-info}/PKG-INFO +1 -1
  13. {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/SOURCES.txt +8 -0
  14. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_declaration_parser.py +40 -2
  15. simdref-0.0.21/tests/test_cuda_doc_blocks.py +121 -0
  16. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_docs_guide.py +26 -4
  17. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_docs_math.py +91 -11
  18. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_join.py +75 -3
  19. {simdref-0.0.20 → simdref-0.0.21}/tests/test_detail_metadata.py +2 -1
  20. simdref-0.0.21/tests/test_doc_entry_merge.py +101 -0
  21. simdref-0.0.21/tests/test_export_site_data.py +109 -0
  22. {simdref-0.0.20 → simdref-0.0.21}/tests/test_lsp_web.py +13 -7
  23. {simdref-0.0.20 → simdref-0.0.21}/tests/test_ptx.py +32 -0
  24. simdref-0.0.21/tests/test_section_order.py +97 -0
  25. {simdref-0.0.20 → simdref-0.0.21}/tests/test_tui.py +28 -10
  26. simdref-0.0.21/tests/test_tui_doc_blocks.py +424 -0
  27. simdref-0.0.21/tests/test_tui_doc_fixes.py +182 -0
  28. simdref-0.0.21/tests/test_tui_doc_fmt.py +373 -0
  29. {simdref-0.0.20 → simdref-0.0.21}/LICENSE +0 -0
  30. {simdref-0.0.20 → simdref-0.0.21}/README.md +0 -0
  31. {simdref-0.0.20 → simdref-0.0.21}/setup.cfg +0 -0
  32. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/__init__.py +0 -0
  33. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/__main__.py +0 -0
  34. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/annotate.py +0 -0
  35. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/arm_instructions.py +0 -0
  36. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/asm_identity.py +0 -0
  37. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/asmline.py +0 -0
  38. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/catkey.py +0 -0
  39. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/cli.py +0 -0
  40. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/filters.py +0 -0
  41. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/htmltext.py +0 -0
  42. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ingest.py +0 -0
  43. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ingest_pdf.py +0 -0
  44. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ingest_sources.py +0 -0
  45. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/lsp.py +0 -0
  46. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/manpages.py +0 -0
  47. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/operands.py +0 -0
  48. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/__init__.py +0 -0
  49. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/base.py +0 -0
  50. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/intel.py +0 -0
  51. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/registry.py +0 -0
  52. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/types.py +0 -0
  53. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfrefs.py +0 -0
  54. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf.py +0 -0
  55. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/__init__.py +0 -0
  56. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/amd_swopt.py +0 -0
  57. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/arm_swopt.py +0 -0
  58. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/cores.py +0 -0
  59. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/instlatx64.py +0 -0
  60. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/intel_guide.py +0 -0
  61. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/intel_optref.py +0 -0
  62. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/llvm_mca.py +0 -0
  63. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
  64. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/merge.py +0 -0
  65. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/queries.py +0 -0
  66. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/regtable.py +0 -0
  67. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/riscv.py +0 -0
  68. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/search.py +0 -0
  69. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/storage.py +0 -0
  70. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/tablecols.py +0 -0
  71. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ui_labels.py +0 -0
  72. {simdref-0.0.20 → simdref-0.0.21}/src/simdref/urls.py +0 -0
  73. {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/dependency_links.txt +0 -0
  74. {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/entry_points.txt +0 -0
  75. {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/requires.txt +0 -0
  76. {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/top_level.txt +0 -0
  77. {simdref-0.0.20 → simdref-0.0.21}/tests/test_a1_sdm_parser.py +0 -0
  78. {simdref-0.0.20 → simdref-0.0.21}/tests/test_a2_perf_rule.py +0 -0
  79. {simdref-0.0.20 → simdref-0.0.21}/tests/test_a4_arm_pdfrefs.py +0 -0
  80. {simdref-0.0.20 → simdref-0.0.21}/tests/test_a5_offline_aarchmrs.py +0 -0
  81. {simdref-0.0.20 → simdref-0.0.21}/tests/test_amd_match_llvm_mc.py +0 -0
  82. {simdref-0.0.20 → simdref-0.0.21}/tests/test_annotate.py +0 -0
  83. {simdref-0.0.20 → simdref-0.0.21}/tests/test_annotate_microarch.py +0 -0
  84. {simdref-0.0.20 → simdref-0.0.21}/tests/test_annotate_positions.py +0 -0
  85. {simdref-0.0.20 → simdref-0.0.21}/tests/test_annotate_scans.py +0 -0
  86. {simdref-0.0.20 → simdref-0.0.21}/tests/test_arm_arch_filter.py +0 -0
  87. {simdref-0.0.20 → simdref-0.0.21}/tests/test_arrangement_tail.py +0 -0
  88. {simdref-0.0.20 → simdref-0.0.21}/tests/test_asm_identity.py +0 -0
  89. {simdref-0.0.20 → simdref-0.0.21}/tests/test_asm_wire.py +0 -0
  90. {simdref-0.0.20 → simdref-0.0.21}/tests/test_asmline.py +0 -0
  91. {simdref-0.0.20 → simdref-0.0.21}/tests/test_audit_coverage.py +0 -0
  92. {simdref-0.0.20 → simdref-0.0.21}/tests/test_auto_update_on_version_change.py +0 -0
  93. {simdref-0.0.20 → simdref-0.0.21}/tests/test_b1_latency_pairs.py +0 -0
  94. {simdref-0.0.20 → simdref-0.0.21}/tests/test_b2_metadata.py +0 -0
  95. {simdref-0.0.20 → simdref-0.0.21}/tests/test_b3_sdm_forms.py +0 -0
  96. {simdref-0.0.20 → simdref-0.0.21}/tests/test_b4_descriptions_provenance.py +0 -0
  97. {simdref-0.0.20 → simdref-0.0.21}/tests/test_b5_linking.py +0 -0
  98. {simdref-0.0.20 → simdref-0.0.21}/tests/test_b5_perf_family.py +0 -0
  99. {simdref-0.0.20 → simdref-0.0.21}/tests/test_b5_sme.py +0 -0
  100. {simdref-0.0.20 → simdref-0.0.21}/tests/test_c0_perf_priority.py +0 -0
  101. {simdref-0.0.20 → simdref-0.0.21}/tests/test_category_filter.py +0 -0
  102. {simdref-0.0.20 → simdref-0.0.21}/tests/test_catkey.py +0 -0
  103. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_bare_query.py +0 -0
  104. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_bootstrap_progress.py +0 -0
  105. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_help.py +0 -0
  106. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_llm.py +0 -0
  107. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_perf_line.py +0 -0
  108. {simdref-0.0.20 → simdref-0.0.21}/tests/test_coverage_live.py +0 -0
  109. {simdref-0.0.20 → simdref-0.0.21}/tests/test_coverage_parity.py +0 -0
  110. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_floors.py +0 -0
  111. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_intrinsics.py +0 -0
  112. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_links.py +0 -0
  113. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_td_expansion.py +0 -0
  114. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_tex.py +0 -0
  115. {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_wiring.py +0 -0
  116. {simdref-0.0.20 → simdref-0.0.21}/tests/test_display.py +0 -0
  117. {simdref-0.0.20 → simdref-0.0.21}/tests/test_display_scanners.py +0 -0
  118. {simdref-0.0.20 → simdref-0.0.21}/tests/test_empty_query_state.py +0 -0
  119. {simdref-0.0.20 → simdref-0.0.21}/tests/test_export.py +0 -0
  120. {simdref-0.0.20 → simdref-0.0.21}/tests/test_export_sm.py +0 -0
  121. {simdref-0.0.20 → simdref-0.0.21}/tests/test_export_sm_column.py +0 -0
  122. {simdref-0.0.20 → simdref-0.0.21}/tests/test_export_viz.py +0 -0
  123. {simdref-0.0.20 → simdref-0.0.21}/tests/test_fetch_retry.py +0 -0
  124. {simdref-0.0.20 → simdref-0.0.21}/tests/test_filters.py +0 -0
  125. {simdref-0.0.20 → simdref-0.0.21}/tests/test_fixui_display.py +0 -0
  126. {simdref-0.0.20 → simdref-0.0.21}/tests/test_htmltext.py +0 -0
  127. {simdref-0.0.20 → simdref-0.0.21}/tests/test_ingest_enc_fields.py +0 -0
  128. {simdref-0.0.20 → simdref-0.0.21}/tests/test_ingest_sources.py +0 -0
  129. {simdref-0.0.20 → simdref-0.0.21}/tests/test_int_arm_isa_label.py +0 -0
  130. {simdref-0.0.20 → simdref-0.0.21}/tests/test_int_core_ids.py +0 -0
  131. {simdref-0.0.20 → simdref-0.0.21}/tests/test_int_ingest_registration.py +0 -0
  132. {simdref-0.0.20 → simdref-0.0.21}/tests/test_int_sdm_family_fallback.py +0 -0
  133. {simdref-0.0.20 → simdref-0.0.21}/tests/test_intel_operation_and_url.py +0 -0
  134. {simdref-0.0.20 → simdref-0.0.21}/tests/test_isa_detect_llvm_mc.py +0 -0
  135. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue2_fixes.py +0 -0
  136. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_51.py +0 -0
  137. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_52.py +0 -0
  138. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_53_54.py +0 -0
  139. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_55.py +0 -0
  140. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_56_57a.py +0 -0
  141. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_57_cpi.py +0 -0
  142. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_60_c1.py +0 -0
  143. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_60_contract.py +0 -0
  144. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_60_crossarch.py +0 -0
  145. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_60_matcher.py +0 -0
  146. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_arm_alias.py +0 -0
  147. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_branch_target.py +0 -0
  148. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_compressed.py +0 -0
  149. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_foreign_arch.py +0 -0
  150. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_identity.py +0 -0
  151. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_m1.py +0 -0
  152. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_m5.py +0 -0
  153. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_perfdata.py +0 -0
  154. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_sme_za.py +0 -0
  155. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_text.py +0 -0
  156. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_timing.py +0 -0
  157. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issues_23_24_25.py +0 -0
  158. {simdref-0.0.20 → simdref-0.0.21}/tests/test_issues_51_57_join.py +0 -0
  159. {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_arm_form_pick.py +0 -0
  160. {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_arm_postindex.py +0 -0
  161. {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_att_movx.py +0 -0
  162. {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_autoupdate.py +0 -0
  163. {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_catalog_gaps.py +0 -0
  164. {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_ingest_isa_merge.py +0 -0
  165. {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_intel_size.py +0 -0
  166. {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_perf_join.py +0 -0
  167. {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_riscv_shared.py +0 -0
  168. {simdref-0.0.20 → simdref-0.0.21}/tests/test_lsp_cuda.py +0 -0
  169. {simdref-0.0.20 → simdref-0.0.21}/tests/test_lsp_ptx.py +0 -0
  170. {simdref-0.0.20 → simdref-0.0.21}/tests/test_lsp_stdio.py +0 -0
  171. {simdref-0.0.20 → simdref-0.0.21}/tests/test_models.py +0 -0
  172. {simdref-0.0.20 → simdref-0.0.21}/tests/test_no_catalog_row.py +0 -0
  173. {simdref-0.0.20 → simdref-0.0.21}/tests/test_objdump_listings.py +0 -0
  174. {simdref-0.0.20 → simdref-0.0.21}/tests/test_operands.py +0 -0
  175. {simdref-0.0.20 → simdref-0.0.21}/tests/test_pdfparse.py +0 -0
  176. {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf.py +0 -0
  177. {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_kind_toggle.py +0 -0
  178. {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_amd_swopt.py +0 -0
  179. {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_arm_swopt.py +0 -0
  180. {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_instlatx64.py +0 -0
  181. {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_intel_guide.py +0 -0
  182. {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_intel_optref.py +0 -0
  183. {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_sources.py +0 -0
  184. {simdref-0.0.20 → simdref-0.0.21}/tests/test_presentation.py +0 -0
  185. {simdref-0.0.20 → simdref-0.0.21}/tests/test_preset_chip_sync.py +0 -0
  186. {simdref-0.0.20 → simdref-0.0.21}/tests/test_preset_persistence.py +0 -0
  187. {simdref-0.0.20 → simdref-0.0.21}/tests/test_regtable.py +0 -0
  188. {simdref-0.0.20 → simdref-0.0.21}/tests/test_related_entries.py +0 -0
  189. {simdref-0.0.20 → simdref-0.0.21}/tests/test_release_tag_pin.py +0 -0
  190. {simdref-0.0.20 → simdref-0.0.21}/tests/test_result_row_badges.py +0 -0
  191. {simdref-0.0.20 → simdref-0.0.21}/tests/test_riscv_docs_pages.py +0 -0
  192. {simdref-0.0.20 → simdref-0.0.21}/tests/test_search.py +0 -0
  193. {simdref-0.0.20 → simdref-0.0.21}/tests/test_search_pushdown.py +0 -0
  194. {simdref-0.0.20 → simdref-0.0.21}/tests/test_shared_source_helpers.py +0 -0
  195. {simdref-0.0.20 → simdref-0.0.21}/tests/test_sm_filter.py +0 -0
  196. {simdref-0.0.20 → simdref-0.0.21}/tests/test_source_kind_filter.py +0 -0
  197. {simdref-0.0.20 → simdref-0.0.21}/tests/test_source_validation.py +0 -0
  198. {simdref-0.0.20 → simdref-0.0.21}/tests/test_storage_alnum.py +0 -0
  199. {simdref-0.0.20 → simdref-0.0.21}/tests/test_storage_payload.py +0 -0
  200. {simdref-0.0.20 → simdref-0.0.21}/tests/test_storage_schema.py +0 -0
  201. {simdref-0.0.20 → simdref-0.0.21}/tests/test_tablecols.py +0 -0
  202. {simdref-0.0.20 → simdref-0.0.21}/tests/test_ui_labels_parity.py +0 -0
  203. {simdref-0.0.20 → simdref-0.0.21}/tests/test_update_build_stamp.py +0 -0
  204. {simdref-0.0.20 → simdref-0.0.21}/tests/test_urls.py +0 -0
  205. {simdref-0.0.20 → simdref-0.0.21}/tests/test_vendor_riscv_categories.py +0 -0
  206. {simdref-0.0.20 → simdref-0.0.21}/tests/test_vendor_riscv_sources.py +0 -0
  207. {simdref-0.0.20 → simdref-0.0.21}/tests/test_version_matches_pyproject.py +0 -0
  208. {simdref-0.0.20 → simdref-0.0.21}/tests/test_x86_key_widths.py +0 -0
  209. {simdref-0.0.20 → simdref-0.0.21}/tests/test_x86_linking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simdref
3
- Version: 0.0.20
3
+ Version: 0.0.21
4
4
  Summary: Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, and LSP.
5
5
  Author: Marco
6
6
  License: GPL-3.0-or-later
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simdref"
7
- version = "0.0.20"
7
+ version = "0.0.21"
8
8
  description = "Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, and LSP."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -13,6 +13,7 @@ import json
13
13
  from typing import Any
14
14
 
15
15
  from simdref.cuda_docs import declares_name, tex_to_text
16
+ from simdref.docblocks import merge_doc_entries
16
17
  from simdref.models import IntrinsicRecord
17
18
  from simdref.urls import resolve_url
18
19
 
@@ -218,6 +219,20 @@ def _starts_at_word(token: str, name: str) -> bool:
218
219
  return False
219
220
 
220
221
 
222
+ def _first_paragraph_text(entry: dict[str, Any]) -> str:
223
+ """Text of the first paragraph block of the entry, else "".
224
+
225
+ The summary is a paragraph, never list text: the flat description
226
+ string of a guide entry merges a lead-in paragraph with the list
227
+ items that follow it (the atomicAdd steps).
228
+ """
229
+ for section in entry.get("sections") or []:
230
+ for block in section.get("blocks") or []:
231
+ if block.get("kind") == "paragraph" and str(block.get("text") or "").strip():
232
+ return str(block["text"])
233
+ return ""
234
+
235
+
221
236
  def apply_cuda_docs(
222
237
  rows: list[IntrinsicRecord], calls: dict[str, list[str]], docs: dict[str, Any]
223
238
  ) -> None:
@@ -259,11 +274,15 @@ def apply_cuda_docs(
259
274
  anchor = str(entry.get("anchor") or "")
260
275
  page = str(entry.get("page") or "")
261
276
  row.url = resolve_url(page, "#" + anchor) if anchor else resolve_url(page)
262
- if entry.get("signature"):
263
- row.signature = str(entry["signature"])
277
+ signature = str(entry.get("signature") or "")
278
+ # A row shows a declaration of its own name, never an example
279
+ # call or a sibling's declaration: the header signature stays
280
+ # when the doc entry declares another name.
281
+ if signature and declares_name(signature, name):
282
+ row.signature = signature
264
283
  description = str(entry.get("description") or "")
265
284
  summary = str(entry.get("summary") or "")
266
- row.description = _first_sentence(summary or description)
285
+ row.description = _first_sentence(summary or _first_paragraph_text(entry) or description)
267
286
  sections: dict[str, str] = {}
268
287
  if description:
269
288
  sections["Description"] = description
@@ -285,10 +304,82 @@ def apply_cuda_docs(
285
304
  if readable != accuracy:
286
305
  row.metadata["doc_math"] = accuracy
287
306
  row.doc_sections = sections
288
- if entry.get("accuracy"):
289
- row.notes = [str(entry["accuracy"])]
290
307
  row.metadata["doc_source"] = source
291
308
  if via:
292
309
  row.metadata["doc_via"] = via
293
- if entry.get("return_value"):
294
- row.metadata["result"] = str(entry["return_value"])
310
+ row.doc_sources = _doc_sources(entry, source, row.url, via, page)
311
+ ulp_blocks = _ulp_doc_entry(ulp_entry)
312
+ if ulp_blocks is not None:
313
+ row.doc_sources.append(ulp_blocks)
314
+
315
+
316
+ def _doc_sources(
317
+ entry: dict[str, Any], source: str, url: str, via: str, page: str
318
+ ) -> list[dict[str, Any]]:
319
+ """The row's own C.1 entry for the page that documents it."""
320
+ sections = entry.get("sections") or []
321
+ block_entry: dict[str, Any] = {
322
+ "source": source,
323
+ "url": url,
324
+ "page": resolve_url(page),
325
+ "title": str(entry.get("title") or ""),
326
+ "heading": str(entry.get("heading") or ""),
327
+ "shared": bool(via) or any(bool(item.get("shared")) for item in sections),
328
+ "sections": sections,
329
+ }
330
+ if entry.get("signature"):
331
+ block_entry["signature"] = str(entry["signature"])
332
+ if entry.get("signature_runs"):
333
+ block_entry["signature_runs"] = entry["signature_runs"]
334
+ if via:
335
+ block_entry["via"] = via
336
+ return [block_entry]
337
+
338
+
339
+ def _ulp_doc_entry(ulp_entry: dict[str, Any]) -> dict[str, Any] | None:
340
+ """The ULP table of the name as one C.1 table entry, else None."""
341
+ rows = [list(cells) for cells in (ulp_entry.get("rows") or [])]
342
+ if not rows:
343
+ return None
344
+ caption = str(ulp_entry.get("caption") or "")
345
+ blocks: list[dict[str, Any]] = [
346
+ {
347
+ "kind": "table",
348
+ "caption": caption,
349
+ # The entry keeps the header as one flat list of cell
350
+ # strings; the table block carries rows of cells. Wrapping
351
+ # once keeps the cells as strings: iterating the flat list
352
+ # would split each cell into its letters.
353
+ "header": [list(ulp_entry.get("header") or [])],
354
+ "rows": rows,
355
+ }
356
+ ]
357
+ accuracy = str(ulp_entry.get("accuracy") or "")
358
+ if ulp_entry.get("math") and accuracy:
359
+ blocks.append({"kind": "math", "text": tex_to_text(accuracy), "tex": accuracy})
360
+ return {
361
+ "source": "ulp",
362
+ "url": str(ulp_entry.get("page") or ""),
363
+ "page": str(ulp_entry.get("page") or ""),
364
+ "title": str(ulp_entry.get("title") or ""),
365
+ "heading": caption,
366
+ "shared": False,
367
+ "sections": [{"heading": "", "shared": False, "blocks": blocks}],
368
+ }
369
+
370
+
371
+ def append_linked_ptx_docs(rows: list[IntrinsicRecord], instructions: list[Any]) -> None:
372
+ """Append the linked PTX entries to the CUDA rows (design C.3).
373
+
374
+ One entry per PTX page. Instruction order first, then document
375
+ order. A row with no linked instruction keeps no PTX entry.
376
+ """
377
+ by_name = {item.mnemonic: item for item in instructions if item.architecture == "ptx"}
378
+ for row in rows:
379
+ for ref in row.instruction_refs:
380
+ target = str(ref.get("name") or "") if isinstance(ref, dict) else str(ref or "")
381
+ linked = by_name.get(target)
382
+ if linked is None:
383
+ continue
384
+ for doc_entry in linked.doc_sources:
385
+ merge_doc_entries(row.doc_sources, doc_entry)
@@ -22,6 +22,7 @@ import hashlib
22
22
  import json
23
23
  import unicodedata
24
24
  from html.parser import HTMLParser
25
+ from typing import Any
25
26
 
26
27
  from simdref.urls import resolve_url
27
28
 
@@ -447,8 +448,7 @@ class _FunctionEntryParser(HTMLParser):
447
448
  self._in_admonition = False
448
449
  name = self._func_name or ""
449
450
  # libdevice pages mark names with the LLVM "@" prefix.
450
- if name.startswith("@"):
451
- name = name[1:]
451
+ name = name.removeprefix("@")
452
452
  accuracy = []
453
453
  for text in self._admonitions:
454
454
  low = text.lower()
@@ -485,16 +485,24 @@ def parse_math_api_page(html: str) -> dict[str, dict[str, str]]:
485
485
 
486
486
  The page holds ``dl cpp function`` entries. The returned mapping
487
487
  holds one entry per function name. A name with several entries
488
- keeps the first one in document order.
488
+ keeps the first one in document order. Each entry carries the C.1
489
+ blocks of its dd, in page order, plus the page heading.
489
490
  """
490
491
  parser = _FunctionEntryParser()
491
492
  parser.feed(html)
492
493
  parser.close()
494
+ from simdref.docblocks import build_tree, math_entry_views, page_title
495
+
496
+ root = build_tree(html)
497
+ views = math_entry_views(root)["entries"]
498
+ title = page_title(root)
493
499
  out = {}
494
- for e in parser.entries:
500
+ for index, e in enumerate(parser.entries):
495
501
  name = e["name"]
496
502
  if not name or name in out:
497
503
  continue
504
+ view = views[index] if index < len(views) else {}
505
+ blocks = view.get("blocks") or []
498
506
  out[name] = {
499
507
  "anchor": e["anchor"],
500
508
  "signature": e["signature"],
@@ -504,7 +512,18 @@ def parse_math_api_page(html: str) -> dict[str, dict[str, str]]:
504
512
  "parameters": e["parameters"],
505
513
  "accuracy": e["accuracy"],
506
514
  "math": list(e["math"]),
515
+ "title": title,
516
+ "heading": name,
517
+ "sections": [
518
+ {
519
+ "heading": "",
520
+ "shared": False,
521
+ "blocks": [{**block, "owner": name} for block in blocks],
522
+ }
523
+ ],
507
524
  }
525
+ if "signature_runs" in view:
526
+ out[name]["signature_runs"] = view["signature_runs"]
508
527
  return out
509
528
 
510
529
 
@@ -699,28 +718,146 @@ def _anchor_for(events: list, index: int, chain: tuple) -> str:
699
718
  return best
700
719
 
701
720
 
721
+ def _skip_paren_group(text: str, start: int) -> int:
722
+ """Return the index just past the balanced ``( ... )`` group at *start*."""
723
+ depth = 0
724
+ for i in range(start, len(text)):
725
+ if text[i] == "(":
726
+ depth += 1
727
+ elif text[i] == ")":
728
+ depth -= 1
729
+ if depth == 0:
730
+ return i + 1
731
+ return len(text)
732
+
733
+
734
+ def drop_attribute_groups(text: str) -> str:
735
+ """Remove every ``__attribute__(( ... ))`` group from the text."""
736
+ out = []
737
+ i = 0
738
+ while True:
739
+ a = text.find("__attribute__(", i)
740
+ if a < 0:
741
+ return "".join(out) + text[i:]
742
+ out.append(text[i:a])
743
+ i = _skip_paren_group(text, a + len("__attribute__"))
744
+
745
+
746
+ _CXX_DECL_QUALIFIERS = frozenset(("static", "inline", "extern", "constexpr"))
747
+ _STATEMENT_KEYWORDS = frozenset(
748
+ (
749
+ "if",
750
+ "else",
751
+ "for",
752
+ "while",
753
+ "switch",
754
+ "return",
755
+ "do",
756
+ "goto",
757
+ "break",
758
+ "continue",
759
+ "case",
760
+ "default",
761
+ "sizeof",
762
+ "try",
763
+ "catch",
764
+ "throw",
765
+ )
766
+ )
767
+
768
+
769
+ def declaration_qualifiers(text: str) -> set[str]:
770
+ """The qualifier tokens of one C source text.
771
+
772
+ The C++ declaration qualifiers plus every token a qualifier macro
773
+ of the text expands to. A macro qualifies when its expansion holds
774
+ only C++ qualifiers and ``__``-prefixed tokens: the ``__DEVICE__``
775
+ pattern. No hand list of CUDA macros. The text must have its
776
+ comments stripped.
777
+ """
778
+ out: set[str] = set(_CXX_DECL_QUALIFIERS)
779
+ buf = ""
780
+ pending = text.split("\n") + [""]
781
+ for raw in pending:
782
+ line = raw.rstrip()
783
+ buf = buf + " " + line.strip() if buf else line
784
+ if line.endswith("\\") and not line.endswith("\\\\"):
785
+ buf = buf[:-1]
786
+ continue
787
+ define = buf.strip()
788
+ buf = ""
789
+ if not define.startswith("#define "):
790
+ continue
791
+ tokens = drop_attribute_groups(define[len("#define ") :]).split()
792
+ if tokens and all(
793
+ _is_identifier(token) and (token in _CXX_DECL_QUALIFIERS or token.startswith("__"))
794
+ for token in tokens
795
+ ):
796
+ out.update(tokens)
797
+ return out
798
+
799
+
800
+ def _call_params(text: str, start: int) -> str | None:
801
+ """Parameter text of the call group that opens at *start*, else None."""
802
+ depth = 0
803
+ for i in range(start, len(text)):
804
+ c = text[i]
805
+ if c == "(":
806
+ depth += 1
807
+ elif c == ")":
808
+ depth -= 1
809
+ if depth == 0:
810
+ return text[start + 1 : i]
811
+ return None
812
+
813
+
814
+ def normalize_declaration(chunk: str, qualifiers: set[str]) -> tuple[list[str], str, str] | None:
815
+ """One declaration normalized: (return type tokens, name, params).
816
+
817
+ Joins the line breaks, splits on whitespace, drops the attribute
818
+ groups and the leading qualifier tokens, then reads the return
819
+ type, the name before the call parens and the parameter text. A
820
+ leading ``__``-prefixed token is a qualifier when a type follows it,
821
+ so a macro qualifier needs no name list. None when the text declares
822
+ nothing: no call parens, a statement keyword shape, or a return
823
+ type that is not a type.
824
+ """
825
+ text = drop_attribute_groups(" ".join(chunk.split()))
826
+ open_at = text.find("(")
827
+ if open_at < 0:
828
+ return None
829
+ params = _call_params(text, open_at)
830
+ if params is None:
831
+ return None
832
+ lead = text[:open_at].rstrip()
833
+ stop = len(lead)
834
+ while stop > 0 and (lead[stop - 1].isalnum() or lead[stop - 1] == "_"):
835
+ stop -= 1
836
+ name = lead[stop:]
837
+ if not name or name[0].isdigit() or name in _STATEMENT_KEYWORDS:
838
+ return None
839
+ tokens = lead[:stop].split()
840
+ cut = 0
841
+ while cut < len(tokens) - 1 and (tokens[cut] in qualifiers or tokens[cut].startswith("__")):
842
+ cut += 1
843
+ type_tokens = tokens[cut:]
844
+ for token in type_tokens:
845
+ if token in _STATEMENT_KEYWORDS:
846
+ return None
847
+ if any(not (c.isalnum() or c == "_" or c in "*&<>:") for c in token):
848
+ return None
849
+ return type_tokens, name, " ".join(params.split())
850
+
851
+
702
852
  def declares_name(text: str, name: str) -> bool:
703
- """The text declares the name: a type word precedes its call.
853
+ """The text declares the name: a return type precedes its call.
704
854
 
705
- A declaration has a type word before the name. An example call has
706
- nothing or a member dot before it, such as ``block.sync()``. The
707
- doc join uses this to tell a function entry from an example line.
855
+ Routed through the normalizer. A declaration has a return type
856
+ before the name. An example call has nothing before it. The doc
857
+ join uses this to tell a function entry from an example line.
708
858
  """
709
- idx = text.find(name)
710
- while idx != -1:
711
- j = idx + len(name)
712
- while j < len(text) and text[j] == " ":
713
- j += 1
714
- if j < len(text) and text[j] == "(":
715
- break
716
- idx = text.find(name, idx + 1)
717
- if idx <= 0:
718
- return False
719
- before = text[:idx].rstrip()
720
- if not before:
721
- return False
722
- prev = before[-1]
723
- return prev.isalnum() or prev in ">_:"
859
+ normalized = normalize_declaration(text, declaration_qualifiers(text))
860
+ return normalized is not None and normalized[1] == name and bool(normalized[0])
724
861
 
725
862
 
726
863
  def parse_guide_page(html: str) -> dict[str, dict[str, str]]:
@@ -741,16 +878,13 @@ def parse_guide_page(html: str) -> dict[str, dict[str, str]]:
741
878
  events = parser.events
742
879
 
743
880
  def _pick_signature(lines, name):
744
- fallback = ""
745
881
  for line in lines:
746
882
  if not _has_token(line, name):
747
883
  continue
748
884
  clean_line = _clean(line)
749
885
  if declares_name(clean_line, name):
750
886
  return clean_line
751
- if not fallback:
752
- fallback = clean_line
753
- return fallback
887
+ return ""
754
888
 
755
889
  out = {}
756
890
  for require_decl in (True, False):
@@ -797,6 +931,7 @@ def parse_guide_page(html: str) -> dict[str, dict[str, str]]:
797
931
  "math": ["description"]
798
932
  if any(k in parser.math_events for k in desc_idx)
799
933
  else [],
934
+ "section": chain[-1] if chain else "",
800
935
  }
801
936
  for i, (kind, ids, text) in enumerate(events):
802
937
  if kind != "p" or not ids or not text.strip():
@@ -811,6 +946,33 @@ def parse_guide_page(html: str) -> dict[str, dict[str, str]]:
811
946
  "signature": "",
812
947
  "description": _clean(text),
813
948
  "math": ["description"] if i in parser.math_events else [],
949
+ "section": chain[-1] if chain else "",
950
+ }
951
+ from simdref.docblocks import build_tree, cut_runs, guide_views, page_title
952
+
953
+ root = build_tree(html)
954
+ views = guide_views(root)
955
+ title = page_title(root)
956
+ for name, entry in list(out.items()):
957
+ section = entry.pop("section", "")
958
+ view = views.get(section) or {}
959
+ entry["title"] = title
960
+ entry["heading"] = str(view.get("heading") or "")
961
+ entry["summary"] = str((view.get("briefs") or {}).get(name) or "")
962
+ entry["sections"] = cut_runs(view.get("runs") or [], name)
963
+ for section, view in views.items():
964
+ for name, brief in (view.get("briefs") or {}).items():
965
+ if name in out:
966
+ continue
967
+ out[name] = {
968
+ "anchor": section,
969
+ "signature": "",
970
+ "description": "",
971
+ "math": [],
972
+ "title": title,
973
+ "heading": str(view.get("heading") or ""),
974
+ "summary": str(brief or ""),
975
+ "sections": cut_runs(view.get("runs") or [], name),
814
976
  }
815
977
  return out
816
978
 
@@ -882,7 +1044,7 @@ class _UlpTableParser(HTMLParser):
882
1044
  self._cell.append(data)
883
1045
 
884
1046
 
885
- def parse_ulp_tables(html: str) -> dict[str, dict[str, str]]:
1047
+ def parse_ulp_tables(html: str) -> dict[str, dict[str, Any]]:
886
1048
  """Parse the ULP tables of a page into a name token to entry map.
887
1049
 
888
1050
  Each ``table`` with a ``caption`` takes part. The header row names
@@ -890,11 +1052,16 @@ def parse_ulp_tables(html: str) -> dict[str, dict[str, str]]:
890
1052
  every whole identifier token of its function cell to the text of
891
1053
  the accuracy cell and the table caption. A row without an accuracy
892
1054
  cell is skipped. A name token in several rows keeps the first row.
1055
+ The entry keeps the page title, the header row and every matching
1056
+ data row, for the C.1 table block.
893
1057
  """
894
1058
  parser = _UlpTableParser()
895
1059
  parser.feed(html)
896
1060
  parser.close()
897
- out = {}
1061
+ from simdref.docblocks import build_tree, page_title
1062
+
1063
+ title = page_title(build_tree(html))
1064
+ out: dict[str, dict[str, Any]] = {}
898
1065
  for table in parser.tables:
899
1066
  if table["caption"] is None:
900
1067
  continue
@@ -902,11 +1069,17 @@ def parse_ulp_tables(html: str) -> dict[str, dict[str, str]]:
902
1069
  func_col = 0
903
1070
  acc_col = None
904
1071
  saw_header = False
1072
+ header_cells: list[str] = []
905
1073
  for row in table["rows"]:
906
- cells = [text for _, text, _ in row]
1074
+ raw_cells = [text for _, text, _ in row]
1075
+ # The block cells read math as unicode, the way inline runs
1076
+ # render it. The accuracy field keeps the raw TeX, so the
1077
+ # math block can render it again.
1078
+ cells = [_clean(tex_to_text(text)) if math else text for _, text, math in row]
907
1079
  is_header = any(is_th for is_th, _, _ in row)
908
1080
  if is_header and not saw_header:
909
1081
  saw_header = True
1082
+ header_cells = cells
910
1083
  for idx, (is_th, text, _) in enumerate(row):
911
1084
  low = text.lower()
912
1085
  if is_th and "function" in low:
@@ -920,16 +1093,16 @@ def parse_ulp_tables(html: str) -> dict[str, dict[str, str]]:
920
1093
  continue
921
1094
  if is_header or not cells:
922
1095
  continue
923
- fidx = func_col if func_col < len(cells) else 0
1096
+ fidx = func_col if func_col < len(raw_cells) else 0
924
1097
  acc = None
925
1098
  acc_math = False
926
- if acc_col is not None and acc_col < len(cells) and acc_col != fidx:
927
- acc = cells[acc_col]
1099
+ if acc_col is not None and acc_col < len(raw_cells) and acc_col != fidx:
1100
+ acc = raw_cells[acc_col]
928
1101
  acc_math = row[acc_col][2]
929
1102
  else:
930
1103
  # No accuracy column in the header. Take the first row
931
1104
  # cell that names an accuracy or a ULP bound.
932
- for idx, text in enumerate(cells):
1105
+ for idx, text in enumerate(raw_cells):
933
1106
  if idx == fidx:
934
1107
  continue
935
1108
  low = text.lower()
@@ -939,13 +1112,18 @@ def parse_ulp_tables(html: str) -> dict[str, dict[str, str]]:
939
1112
  break
940
1113
  if acc is None:
941
1114
  continue
942
- for token in _identifier_tokens(cells[fidx]):
1115
+ for token in _identifier_tokens(raw_cells[fidx]):
943
1116
  if token not in out:
944
1117
  out[token] = {
945
1118
  "accuracy": acc,
946
1119
  "caption": caption,
947
1120
  "math": ["accuracy"] if acc_math else [],
1121
+ "title": title,
1122
+ "header": header_cells,
1123
+ "rows": [cells],
948
1124
  }
1125
+ elif out[token]["caption"] == caption:
1126
+ out[token]["rows"].append(cells)
949
1127
  return out
950
1128
 
951
1129