simdref 0.0.18__tar.gz → 0.0.20__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 (203) hide show
  1. {simdref-0.0.18/src/simdref.egg-info → simdref-0.0.20}/PKG-INFO +1 -1
  2. {simdref-0.0.18 → simdref-0.0.20}/pyproject.toml +1 -1
  3. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/asmline.py +132 -12
  4. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/cli.py +52 -16
  5. simdref-0.0.20/src/simdref/cuda.py +294 -0
  6. simdref-0.0.20/src/simdref/cuda_docs.py +1158 -0
  7. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/display.py +15 -14
  8. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/export.py +42 -26
  9. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/filters.py +190 -4
  10. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ingest_catalog.py +103 -3
  11. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ingest_sources.py +32 -0
  12. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/lsp.py +125 -12
  13. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ptx.py +279 -17
  14. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/tui.py +549 -24
  15. simdref-0.0.20/src/simdref/urls.py +46 -0
  16. {simdref-0.0.18 → simdref-0.0.20/src/simdref.egg-info}/PKG-INFO +1 -1
  17. {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/SOURCES.txt +22 -0
  18. simdref-0.0.20/tests/test_annotate_microarch.py +55 -0
  19. simdref-0.0.20/tests/test_arm_arch_filter.py +159 -0
  20. simdref-0.0.20/tests/test_category_filter.py +84 -0
  21. simdref-0.0.20/tests/test_cli_perf_line.py +37 -0
  22. simdref-0.0.20/tests/test_cuda_docs_guide.py +140 -0
  23. simdref-0.0.20/tests/test_cuda_docs_math.py +382 -0
  24. simdref-0.0.20/tests/test_cuda_floors.py +145 -0
  25. simdref-0.0.20/tests/test_cuda_join.py +145 -0
  26. simdref-0.0.20/tests/test_cuda_links.py +75 -0
  27. simdref-0.0.20/tests/test_cuda_tex.py +72 -0
  28. {simdref-0.0.18 → simdref-0.0.20}/tests/test_cuda_wiring.py +24 -0
  29. simdref-0.0.20/tests/test_detail_metadata.py +90 -0
  30. simdref-0.0.20/tests/test_empty_query_state.py +69 -0
  31. {simdref-0.0.18 → simdref-0.0.20}/tests/test_export.py +21 -20
  32. simdref-0.0.20/tests/test_export_sm.py +72 -0
  33. simdref-0.0.20/tests/test_export_sm_column.py +86 -0
  34. {simdref-0.0.18 → simdref-0.0.20}/tests/test_filters.py +21 -3
  35. {simdref-0.0.18 → simdref-0.0.20}/tests/test_lsp_ptx.py +19 -0
  36. {simdref-0.0.18 → simdref-0.0.20}/tests/test_lsp_stdio.py +212 -3
  37. simdref-0.0.20/tests/test_perf_kind_toggle.py +84 -0
  38. {simdref-0.0.18 → simdref-0.0.20}/tests/test_presentation.py +10 -0
  39. simdref-0.0.20/tests/test_preset_chip_sync.py +45 -0
  40. simdref-0.0.20/tests/test_ptx.py +431 -0
  41. simdref-0.0.20/tests/test_related_entries.py +121 -0
  42. simdref-0.0.20/tests/test_result_row_badges.py +71 -0
  43. simdref-0.0.20/tests/test_sm_filter.py +297 -0
  44. {simdref-0.0.18 → simdref-0.0.20}/tests/test_tui.py +163 -0
  45. simdref-0.0.20/tests/test_urls.py +71 -0
  46. simdref-0.0.18/src/simdref/cuda.py +0 -50
  47. simdref-0.0.18/tests/test_ptx.py +0 -253
  48. {simdref-0.0.18 → simdref-0.0.20}/LICENSE +0 -0
  49. {simdref-0.0.18 → simdref-0.0.20}/README.md +0 -0
  50. {simdref-0.0.18 → simdref-0.0.20}/setup.cfg +0 -0
  51. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/__init__.py +0 -0
  52. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/__main__.py +0 -0
  53. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/annotate.py +0 -0
  54. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/arm_instructions.py +0 -0
  55. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/asm_identity.py +0 -0
  56. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/catkey.py +0 -0
  57. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/htmltext.py +0 -0
  58. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ingest.py +0 -0
  59. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ingest_pdf.py +0 -0
  60. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/manpages.py +0 -0
  61. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/models.py +0 -0
  62. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/operands.py +0 -0
  63. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/__init__.py +0 -0
  64. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/base.py +0 -0
  65. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/intel.py +0 -0
  66. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/registry.py +0 -0
  67. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfparse/types.py +0 -0
  68. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/pdfrefs.py +0 -0
  69. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf.py +0 -0
  70. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/__init__.py +0 -0
  71. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/amd_swopt.py +0 -0
  72. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/arm_swopt.py +0 -0
  73. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/cores.py +0 -0
  74. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/instlatx64.py +0 -0
  75. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/intel_guide.py +0 -0
  76. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/intel_optref.py +0 -0
  77. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/llvm_mca.py +0 -0
  78. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
  79. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/perf_sources/merge.py +0 -0
  80. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/queries.py +0 -0
  81. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/regtable.py +0 -0
  82. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/riscv.py +0 -0
  83. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/search.py +0 -0
  84. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/storage.py +0 -0
  85. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/tablecols.py +0 -0
  86. {simdref-0.0.18 → simdref-0.0.20}/src/simdref/ui_labels.py +0 -0
  87. {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/dependency_links.txt +0 -0
  88. {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/entry_points.txt +0 -0
  89. {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/requires.txt +0 -0
  90. {simdref-0.0.18 → simdref-0.0.20}/src/simdref.egg-info/top_level.txt +0 -0
  91. {simdref-0.0.18 → simdref-0.0.20}/tests/test_a1_sdm_parser.py +0 -0
  92. {simdref-0.0.18 → simdref-0.0.20}/tests/test_a2_perf_rule.py +0 -0
  93. {simdref-0.0.18 → simdref-0.0.20}/tests/test_a4_arm_pdfrefs.py +0 -0
  94. {simdref-0.0.18 → simdref-0.0.20}/tests/test_a5_offline_aarchmrs.py +0 -0
  95. {simdref-0.0.18 → simdref-0.0.20}/tests/test_amd_match_llvm_mc.py +0 -0
  96. {simdref-0.0.18 → simdref-0.0.20}/tests/test_annotate.py +0 -0
  97. {simdref-0.0.18 → simdref-0.0.20}/tests/test_annotate_positions.py +0 -0
  98. {simdref-0.0.18 → simdref-0.0.20}/tests/test_annotate_scans.py +0 -0
  99. {simdref-0.0.18 → simdref-0.0.20}/tests/test_arrangement_tail.py +0 -0
  100. {simdref-0.0.18 → simdref-0.0.20}/tests/test_asm_identity.py +0 -0
  101. {simdref-0.0.18 → simdref-0.0.20}/tests/test_asm_wire.py +0 -0
  102. {simdref-0.0.18 → simdref-0.0.20}/tests/test_asmline.py +0 -0
  103. {simdref-0.0.18 → simdref-0.0.20}/tests/test_audit_coverage.py +0 -0
  104. {simdref-0.0.18 → simdref-0.0.20}/tests/test_auto_update_on_version_change.py +0 -0
  105. {simdref-0.0.18 → simdref-0.0.20}/tests/test_b1_latency_pairs.py +0 -0
  106. {simdref-0.0.18 → simdref-0.0.20}/tests/test_b2_metadata.py +0 -0
  107. {simdref-0.0.18 → simdref-0.0.20}/tests/test_b3_sdm_forms.py +0 -0
  108. {simdref-0.0.18 → simdref-0.0.20}/tests/test_b4_descriptions_provenance.py +0 -0
  109. {simdref-0.0.18 → simdref-0.0.20}/tests/test_b5_linking.py +0 -0
  110. {simdref-0.0.18 → simdref-0.0.20}/tests/test_b5_perf_family.py +0 -0
  111. {simdref-0.0.18 → simdref-0.0.20}/tests/test_b5_sme.py +0 -0
  112. {simdref-0.0.18 → simdref-0.0.20}/tests/test_c0_perf_priority.py +0 -0
  113. {simdref-0.0.18 → simdref-0.0.20}/tests/test_catkey.py +0 -0
  114. {simdref-0.0.18 → simdref-0.0.20}/tests/test_cli_bare_query.py +0 -0
  115. {simdref-0.0.18 → simdref-0.0.20}/tests/test_cli_bootstrap_progress.py +0 -0
  116. {simdref-0.0.18 → simdref-0.0.20}/tests/test_cli_help.py +0 -0
  117. {simdref-0.0.18 → simdref-0.0.20}/tests/test_cli_llm.py +0 -0
  118. {simdref-0.0.18 → simdref-0.0.20}/tests/test_coverage_live.py +0 -0
  119. {simdref-0.0.18 → simdref-0.0.20}/tests/test_coverage_parity.py +0 -0
  120. {simdref-0.0.18 → simdref-0.0.20}/tests/test_cuda_declaration_parser.py +0 -0
  121. {simdref-0.0.18 → simdref-0.0.20}/tests/test_cuda_intrinsics.py +0 -0
  122. {simdref-0.0.18 → simdref-0.0.20}/tests/test_cuda_td_expansion.py +0 -0
  123. {simdref-0.0.18 → simdref-0.0.20}/tests/test_display.py +0 -0
  124. {simdref-0.0.18 → simdref-0.0.20}/tests/test_display_scanners.py +0 -0
  125. {simdref-0.0.18 → simdref-0.0.20}/tests/test_export_viz.py +0 -0
  126. {simdref-0.0.18 → simdref-0.0.20}/tests/test_fetch_retry.py +0 -0
  127. {simdref-0.0.18 → simdref-0.0.20}/tests/test_fixui_display.py +0 -0
  128. {simdref-0.0.18 → simdref-0.0.20}/tests/test_htmltext.py +0 -0
  129. {simdref-0.0.18 → simdref-0.0.20}/tests/test_ingest_enc_fields.py +0 -0
  130. {simdref-0.0.18 → simdref-0.0.20}/tests/test_ingest_sources.py +0 -0
  131. {simdref-0.0.18 → simdref-0.0.20}/tests/test_int_arm_isa_label.py +0 -0
  132. {simdref-0.0.18 → simdref-0.0.20}/tests/test_int_core_ids.py +0 -0
  133. {simdref-0.0.18 → simdref-0.0.20}/tests/test_int_ingest_registration.py +0 -0
  134. {simdref-0.0.18 → simdref-0.0.20}/tests/test_int_sdm_family_fallback.py +0 -0
  135. {simdref-0.0.18 → simdref-0.0.20}/tests/test_intel_operation_and_url.py +0 -0
  136. {simdref-0.0.18 → simdref-0.0.20}/tests/test_isa_detect_llvm_mc.py +0 -0
  137. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue2_fixes.py +0 -0
  138. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_51.py +0 -0
  139. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_52.py +0 -0
  140. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_53_54.py +0 -0
  141. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_55.py +0 -0
  142. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_56_57a.py +0 -0
  143. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_57_cpi.py +0 -0
  144. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_60_c1.py +0 -0
  145. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_60_contract.py +0 -0
  146. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_60_crossarch.py +0 -0
  147. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_60_matcher.py +0 -0
  148. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_arm_alias.py +0 -0
  149. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_branch_target.py +0 -0
  150. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_compressed.py +0 -0
  151. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_foreign_arch.py +0 -0
  152. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_identity.py +0 -0
  153. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_m1.py +0 -0
  154. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_m5.py +0 -0
  155. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_perfdata.py +0 -0
  156. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_sme_za.py +0 -0
  157. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_text.py +0 -0
  158. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issue_65_timing.py +0 -0
  159. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issues_23_24_25.py +0 -0
  160. {simdref-0.0.18 → simdref-0.0.20}/tests/test_issues_51_57_join.py +0 -0
  161. {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_arm_form_pick.py +0 -0
  162. {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_arm_postindex.py +0 -0
  163. {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_att_movx.py +0 -0
  164. {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_autoupdate.py +0 -0
  165. {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_catalog_gaps.py +0 -0
  166. {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_ingest_isa_merge.py +0 -0
  167. {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_intel_size.py +0 -0
  168. {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_perf_join.py +0 -0
  169. {simdref-0.0.18 → simdref-0.0.20}/tests/test_leftover_riscv_shared.py +0 -0
  170. {simdref-0.0.18 → simdref-0.0.20}/tests/test_lsp_cuda.py +0 -0
  171. {simdref-0.0.18 → simdref-0.0.20}/tests/test_lsp_web.py +0 -0
  172. {simdref-0.0.18 → simdref-0.0.20}/tests/test_models.py +0 -0
  173. {simdref-0.0.18 → simdref-0.0.20}/tests/test_no_catalog_row.py +0 -0
  174. {simdref-0.0.18 → simdref-0.0.20}/tests/test_objdump_listings.py +0 -0
  175. {simdref-0.0.18 → simdref-0.0.20}/tests/test_operands.py +0 -0
  176. {simdref-0.0.18 → simdref-0.0.20}/tests/test_pdfparse.py +0 -0
  177. {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf.py +0 -0
  178. {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_amd_swopt.py +0 -0
  179. {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_arm_swopt.py +0 -0
  180. {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_instlatx64.py +0 -0
  181. {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_intel_guide.py +0 -0
  182. {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_source_intel_optref.py +0 -0
  183. {simdref-0.0.18 → simdref-0.0.20}/tests/test_perf_sources.py +0 -0
  184. {simdref-0.0.18 → simdref-0.0.20}/tests/test_preset_persistence.py +0 -0
  185. {simdref-0.0.18 → simdref-0.0.20}/tests/test_regtable.py +0 -0
  186. {simdref-0.0.18 → simdref-0.0.20}/tests/test_release_tag_pin.py +0 -0
  187. {simdref-0.0.18 → simdref-0.0.20}/tests/test_riscv_docs_pages.py +0 -0
  188. {simdref-0.0.18 → simdref-0.0.20}/tests/test_search.py +0 -0
  189. {simdref-0.0.18 → simdref-0.0.20}/tests/test_search_pushdown.py +0 -0
  190. {simdref-0.0.18 → simdref-0.0.20}/tests/test_shared_source_helpers.py +0 -0
  191. {simdref-0.0.18 → simdref-0.0.20}/tests/test_source_kind_filter.py +0 -0
  192. {simdref-0.0.18 → simdref-0.0.20}/tests/test_source_validation.py +0 -0
  193. {simdref-0.0.18 → simdref-0.0.20}/tests/test_storage_alnum.py +0 -0
  194. {simdref-0.0.18 → simdref-0.0.20}/tests/test_storage_payload.py +0 -0
  195. {simdref-0.0.18 → simdref-0.0.20}/tests/test_storage_schema.py +0 -0
  196. {simdref-0.0.18 → simdref-0.0.20}/tests/test_tablecols.py +0 -0
  197. {simdref-0.0.18 → simdref-0.0.20}/tests/test_ui_labels_parity.py +0 -0
  198. {simdref-0.0.18 → simdref-0.0.20}/tests/test_update_build_stamp.py +0 -0
  199. {simdref-0.0.18 → simdref-0.0.20}/tests/test_vendor_riscv_categories.py +0 -0
  200. {simdref-0.0.18 → simdref-0.0.20}/tests/test_vendor_riscv_sources.py +0 -0
  201. {simdref-0.0.18 → simdref-0.0.20}/tests/test_version_matches_pyproject.py +0 -0
  202. {simdref-0.0.18 → simdref-0.0.20}/tests/test_x86_key_widths.py +0 -0
  203. {simdref-0.0.18 → simdref-0.0.20}/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.18
3
+ Version: 0.0.20
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.18"
7
+ version = "0.0.20"
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"
@@ -174,28 +174,96 @@ def c_asm_zone_at(text: str, offset: int) -> str | None:
174
174
 
175
175
  'template' is an asm(...) template string, 'msvc' an MSVC __asm block
176
176
  or line, 'operands' the operand and clobber lists after the first ':'
177
- of an asm(...) call. None is plain code. Shares _asm_call_at with
177
+ of an asm(...) call, 'comment' a C comment, 'string' a string literal
178
+ that is no asm template. None is plain code. Shares _asm_call_at with
178
179
  c_asm_literals, so hover and completion parse the same calls.
179
180
  """
181
+ span = c_span_at(text, offset)
182
+ if span == "comment":
183
+ return span
184
+ zone = c_asm_zone_span_at(text, offset)
185
+ return zone[0] if zone is not None else span
186
+
187
+
188
+ def c_asm_zone_span_at(text: str, offset: int) -> tuple[str, int, int] | None:
189
+ """(kind, low, high) of the asm zone holding the offset, or None.
190
+
191
+ The same walk as c_asm_zone_at. lsp.py reads the bounds: each MSVC
192
+ __asm statement starts at its own zone's low bound.
193
+ """
180
194
  masked = _mask_c_comments(text)
181
195
  index = 0
182
196
  while index < len(masked):
183
197
  hit = _asm_call_at(masked, index)
184
198
  if hit is not None:
185
199
  block_end, literals, zones = hit
186
- for kind, low, high in zones:
187
- if low <= offset <= high:
188
- return kind
189
- for start, _body in literals:
190
- if start <= offset <= start + len(_body):
191
- return "template"
200
+ for zone in zones:
201
+ if zone[1] <= offset <= zone[2]:
202
+ return zone
203
+ for start, body in literals:
204
+ if start <= offset <= start + len(body):
205
+ return "template", start, start + len(body)
192
206
  index = block_end
193
207
  else:
194
208
  index += 1
195
209
  return None
196
210
 
197
211
 
212
+ def c_span_at(text: str, offset: int) -> str | None:
213
+ """'comment' or 'string' when a cursor offset sits inside one, else None.
214
+
215
+ Same scan as _mask_c_comments. A cursor right after the opening
216
+ delimiter is inside; a cursor after the closing one is outside. An
217
+ unterminated block comment runs to the end, an unterminated string to
218
+ the end of its line: both are still being typed. A char literal and a
219
+ raw string are their own tokens: the double quote inside the char
220
+ literal '"' never opens a string, and a raw string keeps its quotes.
221
+ """
222
+ index = 0
223
+ while index < offset:
224
+ line_end = text.find("\n", index)
225
+ line_end = len(text) if line_end == -1 else line_end
226
+ if text[index] == "'":
227
+ end = _char_end(text, index) or line_end + 1
228
+ if index < offset < end:
229
+ return "string"
230
+ elif text[index] == '"':
231
+ end = _raw_string_end(text, index)
232
+ if end is None:
233
+ end = _string_end(text, index) or line_end + 1
234
+ if index < offset < end:
235
+ return "string"
236
+ elif text.startswith("//", index):
237
+ end = line_end
238
+ if index + 2 <= offset <= end:
239
+ return "comment"
240
+ elif text.startswith("/*", index):
241
+ close = text.find("*/", index + 2)
242
+ end = len(text) if close == -1 else close + 2
243
+ if index + 2 <= offset <= (end if close == -1 else close):
244
+ return "comment"
245
+ else:
246
+ index += 1
247
+ continue
248
+ index = max(end, index + 1)
249
+ return None
250
+
251
+
198
252
  _QUALIFIERS = ("volatile", "__volatile__", "goto", "inline")
253
+ _ASM_KEYWORDS = ("__asm__", "__asm", "asm")
254
+
255
+
256
+ def _asm_keyword_at(text: str, index: int) -> str | None:
257
+ """The asm keyword at index with word boundaries on both sides, or None."""
258
+ if index > 0 and (text[index - 1].isalnum() or text[index - 1] == "_"):
259
+ return None
260
+ for keyword in _ASM_KEYWORDS:
261
+ if text.startswith(keyword, index):
262
+ after = index + len(keyword)
263
+ tail = text[after : after + 1]
264
+ if not (tail.isalnum() or tail == "_"):
265
+ return keyword
266
+ return None
199
267
 
200
268
 
201
269
  def _asm_call_at(
@@ -214,7 +282,7 @@ def _asm_call_at(
214
282
  and (text[index - 1].isalnum() or text[index - 1] == "_")
215
283
  ):
216
284
  return None
217
- for keyword in ("__asm__", "__asm", "asm"):
285
+ for keyword in _ASM_KEYWORDS:
218
286
  if text.startswith(keyword, index):
219
287
  break
220
288
  else:
@@ -240,9 +308,12 @@ def _asm_call_at(
240
308
  return None
241
309
  return close + 1, [], [("msvc", cursor + 1, close)]
242
310
  if text[cursor : cursor + 1] != "(":
243
- # MSVC __asm <insn> line: the instruction runs to end of line.
244
- end = index
311
+ # MSVC __asm <insn>: the statement runs to end of line, or to the
312
+ # next __asm keyword, which starts its own statement on the row.
313
+ end = cursor
245
314
  while end < len(text) and text[end] != "\n":
315
+ if _asm_keyword_at(text, end):
316
+ break
246
317
  end += 1
247
318
  return end, [], [("msvc", cursor, end)]
248
319
  cursor = _skip_space(text, cursor + 1)
@@ -353,14 +424,63 @@ def _string_end(text: str, index: int, spans_lines: bool = False) -> int | None:
353
424
  return None
354
425
 
355
426
 
427
+ def _char_end(text: str, index: int) -> int | None:
428
+ """The index after the closing quote of the char literal at index, or None.
429
+
430
+ A char literal is one token like a string: its quote never opens a
431
+ string, and an escape pair ("\\'", "\\"") counts as one character.
432
+ """
433
+ if text[index : index + 1] != "'":
434
+ return None
435
+ cursor = index + 1
436
+ while cursor < len(text):
437
+ char = text[cursor]
438
+ if char == "\\":
439
+ cursor += 2
440
+ elif char == "'":
441
+ return cursor + 1
442
+ elif char == "\n":
443
+ return None
444
+ else:
445
+ cursor += 1
446
+ return None
447
+
448
+
449
+ def _raw_string_end(text: str, index: int) -> int | None:
450
+ """The index after the R"delim(...)delim" raw string whose quote is at index.
451
+
452
+ None when the shape is not a raw string: the caller then reads the
453
+ quote as a plain string. An unterminated raw string runs one past the
454
+ end of the text, because its body spans lines.
455
+ """
456
+ if text[index - 1 : index] != "R":
457
+ return None
458
+ open_paren = text.find("(", index + 1, index + 18)
459
+ if open_paren == -1:
460
+ return None
461
+ delim = text[index + 1 : open_paren]
462
+ if len(delim) > 16 or any(char.isspace() or char in "()\\" for char in delim):
463
+ return None
464
+ close = text.find(")" + delim + '"', open_paren + 1)
465
+ return len(text) + 1 if close == -1 else close + len(delim) + 2
466
+
467
+
356
468
  def _mask_c_comments(text: str) -> str:
357
- """Blank C comments to spaces (newlines stay). String literals stay."""
469
+ """Blank C comments to spaces (newlines stay). Literal tokens stay."""
358
470
  out: list[str] = []
359
471
  index = 0
360
472
  while index < len(text):
361
473
  char = text[index]
474
+ if char == "'":
475
+ end = _char_end(text, index)
476
+ if end is not None:
477
+ out.append(text[index:end])
478
+ index = end
479
+ continue
362
480
  if char == '"':
363
- end = _string_end(text, index)
481
+ end = _raw_string_end(text, index)
482
+ if end is None:
483
+ end = _string_end(text, index)
364
484
  if end is not None:
365
485
  out.append(text[index:end])
366
486
  index = end
@@ -917,11 +917,13 @@ def _payload_source_kinds(arch_details) -> list[str]:
917
917
  # ---------------------------------------------------------------------------
918
918
 
919
919
 
920
- def _print_search_results_runtime(conn, query: str, limit: int = 20, as_json: bool = False) -> int:
920
+ def _print_search_results_runtime(
921
+ conn, query: str, limit: int = 20, as_json: bool = False, state=None
922
+ ) -> int:
921
923
  """Render the ranked search table (or JSON) in the search's own
922
924
  relevance order. Returns the number of rows actually printed — callers
923
925
  use 0 to fall through to the no-match branch instead of printing an
924
- empty table."""
926
+ empty table. *state* hides rows the sm filter excludes."""
925
927
  results, intrinsic_map, instruction_map = _search_runtime(conn, query, limit=limit)
926
928
  prepared_rows = []
927
929
  for result in results:
@@ -934,6 +936,8 @@ def _print_search_results_runtime(conn, query: str, limit: int = 20, as_json: bo
934
936
  if item is not None:
935
937
  if not isa_visible(item.isa, show_fp16=SHOW_FP16_ISAS):
936
938
  continue
939
+ if state is not None and not state.matches(item):
940
+ continue
937
941
  arch = display_architecture(item.architecture)
938
942
  isa = display_isa(item.isa)
939
943
  lat, cpi = variant_perf_summary(item.arch_details, perf.record_iclass(item))
@@ -942,6 +946,8 @@ def _print_search_results_runtime(conn, query: str, limit: int = 20, as_json: bo
942
946
  if item is not None:
943
947
  if not isa_visible(item.isa, show_fp16=SHOW_FP16_ISAS):
944
948
  continue
949
+ if state is not None and not state.matches(item):
950
+ continue
945
951
  arch = display_architecture(item.architecture)
946
952
  isa = display_isa(item.isa)
947
953
  lat, cpi = intrinsic_perf_summary_runtime(conn, item, instruction_map)
@@ -987,18 +993,31 @@ def _smart_lookup(
987
993
  preset: str | None = None,
988
994
  arch: str | None = None,
989
995
  as_json: bool = False,
996
+ sm: str | None = None,
990
997
  ) -> int:
991
998
  """Dispatch a bare query.
992
999
 
993
1000
  Exact intrinsic match prints the intrinsic detail and exits 0. Exact
994
1001
  instruction match prints a plain-text (or JSON) summary and exits 0;
995
1002
  ``--arch`` applies to this branch only. Fuzzy hits print the ranked
996
- search list (or JSON) and exit 0, on a TTY or not. Nothing found opens
1003
+ search list (or JSON) and exit 0, on a TTY or not. ``--sm`` hides the
1004
+ records whose sm floor fails the chosen level. Nothing found opens
997
1005
  the TUI on a TTY (never under ``--json``) or exits 2 non-interactively.
998
1006
  """
999
1007
  ensure_runtime()
1008
+ from simdref.filters import FilterState, sm_number
1009
+
1010
+ if sm:
1011
+ try:
1012
+ sm_number(sm)
1013
+ except ValueError:
1014
+ err_console.print(f"--sm {sm!r} is not an sm level (want sm_XX)", style="red")
1015
+ return 1
1016
+ state = FilterState(min_sm=sm or "")
1000
1017
  with contextlib.closing(open_db()) as conn:
1001
1018
  intrinsic = load_intrinsic_from_db(conn, query)
1019
+ if intrinsic is not None and not state.matches(intrinsic):
1020
+ intrinsic = None
1002
1021
  if intrinsic is not None and arch is not None:
1003
1022
  err_console.print("--arch applies to instruction queries only", style="red")
1004
1023
  return 2
@@ -1008,7 +1027,7 @@ def _smart_lookup(
1008
1027
  else:
1009
1028
  render_intrinsic(None, intrinsic, conn=conn)
1010
1029
  return 0
1011
- records = _find_instructions_fast(query)
1030
+ records = [r for r in _find_instructions_fast(query) if state.matches(r)]
1012
1031
  if records:
1013
1032
  return _print_non_interactive_summary(
1014
1033
  query, records=records, arch=arch, as_json=as_json
@@ -1016,7 +1035,7 @@ def _smart_lookup(
1016
1035
  if arch is not None:
1017
1036
  err_console.print("--arch applies to instruction queries only", style="red")
1018
1037
  return 2
1019
- printed = _print_search_results_runtime(conn, query, as_json=as_json)
1038
+ printed = _print_search_results_runtime(conn, query, as_json=as_json, state=state)
1020
1039
  if printed:
1021
1040
  if not as_json and sys.stdin.isatty() and sys.stdout.isatty():
1022
1041
  # A fuzzy match on a TTY opens the TUI (preset as passed, like
@@ -1158,14 +1177,18 @@ def _print_non_interactive_summary(
1158
1177
  typer.echo(f" {tag}: lat={lat_text}c cpi={_fmt_num(cpi)} ports={ports_str}")
1159
1178
  else:
1160
1179
  summary = aggregate_perf(rec, mode="avg", include_modeled=False)
1161
- ports = collect_ports(rec, arch=None, archs_used=summary.archs_used)
1162
- ports_str = ",".join(ports) if ports else "-"
1163
- typer.echo(
1164
- f" avg over {summary.n_archs} arch(s) [{summary.source_kind}]: "
1165
- f"lat={_fmt_num(summary.latency)}c cpi={_fmt_num(summary.cpi)} ports={ports_str}"
1166
- )
1167
- if summary.archs_used:
1168
- typer.echo(f" archs: {', '.join(summary.archs_used)}")
1180
+ if summary.n_archs == 0:
1181
+ # The TUI wording (tui._NO_DATA): no arch holds perf data.
1182
+ typer.echo(" performance: no data")
1183
+ else:
1184
+ ports = collect_ports(rec, arch=None, archs_used=summary.archs_used)
1185
+ ports_str = ",".join(ports) if ports else "-"
1186
+ typer.echo(
1187
+ f" avg over {summary.n_archs} arch(s) [{summary.source_kind}]: "
1188
+ f"lat={_fmt_num(summary.latency)}c cpi={_fmt_num(summary.cpi)} ports={ports_str}"
1189
+ )
1190
+ if summary.archs_used:
1191
+ typer.echo(f" archs: {', '.join(summary.archs_used)}")
1169
1192
  return 0
1170
1193
 
1171
1194
 
@@ -1992,7 +2015,7 @@ def llm_query(
1992
2015
  preset: str = typer.Option(
1993
2016
  None,
1994
2017
  "--preset",
1995
- help="Apply a named preset (default, intel, arm32, arm64, riscv, none, all).",
2018
+ help="Apply a named preset (default, intel, arm32, arm64, riscv, cuda, none, all).",
1996
2019
  ),
1997
2020
  source_kind: str = typer.Option(
1998
2021
  "any", "--source-kind", help="Filter perf rows by provenance: measured, modeled, or any."
@@ -2120,7 +2143,7 @@ def llm_batch(
2120
2143
  preset: str = typer.Option(
2121
2144
  None,
2122
2145
  "--preset",
2123
- help="Apply a named preset (default, intel, arm32, arm64, riscv, none, all).",
2146
+ help="Apply a named preset (default, intel, arm32, arm64, riscv, cuda, none, all).",
2124
2147
  ),
2125
2148
  source_kind: str = typer.Option(
2126
2149
  "any", "--source-kind", help="Filter perf rows by provenance: measured, modeled, or any."
@@ -2471,6 +2494,7 @@ def main() -> int:
2471
2494
  # dispatch.
2472
2495
  initial_preset: str | None = None
2473
2496
  bare_arch: str | None = None
2497
+ bare_sm: str | None = None
2474
2498
  bare_json = False
2475
2499
  _cleaned: list[str] = []
2476
2500
  _i = 0
@@ -2492,6 +2516,14 @@ def main() -> int:
2492
2516
  bare_arch = arg.split("=", 1)[1]
2493
2517
  _i += 1
2494
2518
  continue
2519
+ if arg == "--sm" and _i + 1 < len(argv):
2520
+ bare_sm = argv[_i + 1]
2521
+ _i += 2
2522
+ continue
2523
+ if arg.startswith("--sm="):
2524
+ bare_sm = arg.split("=", 1)[1]
2525
+ _i += 1
2526
+ continue
2495
2527
  if arg == "--json":
2496
2528
  bare_json = True
2497
2529
  _i += 1
@@ -2531,7 +2563,11 @@ def main() -> int:
2531
2563
  if len(argv) > 1 and argv[0].lower() in {"show", "lookup", "info"}:
2532
2564
  argv = argv[1:]
2533
2565
  return _smart_lookup(
2534
- " ".join(argv), preset=initial_preset, arch=bare_arch, as_json=bare_json
2566
+ " ".join(argv),
2567
+ preset=initial_preset,
2568
+ arch=bare_arch,
2569
+ as_json=bare_json,
2570
+ sm=bare_sm,
2535
2571
  )
2536
2572
  if not argv:
2537
2573
  ensure_runtime()
@@ -0,0 +1,294 @@
1
+ """CUDA C++ device intrinsics parsing and derived record rows.
2
+
3
+ Parses the JSON bundle produced by tools/vendor_cuda_sources.py, which
4
+ extracts CUDA API names from four clang headers and resolves PTX
5
+ instruction links by parsing LLVM TableGen .td files. Joins the NVIDIA
6
+ documentation pages, parsed by simdref.cuda_docs, into the rows. The
7
+ join runs at build time. No NVIDIA text is stored in git.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from typing import Any
14
+
15
+ from simdref.cuda_docs import declares_name, tex_to_text
16
+ from simdref.models import IntrinsicRecord
17
+ from simdref.urls import resolve_url
18
+
19
+ BUNDLE_FORMAT = "clang-cuda-headers-v2"
20
+ CUDA_INTRINSICS_DOC_URL = "https://docs.nvidia.com/cuda/cuda-c-programming-guide/"
21
+
22
+
23
+ def parse_cuda_intrinsics_payload(text: str) -> list[IntrinsicRecord]:
24
+ """Parse the CUDA intrinsics JSON bundle into IntrinsicRecord rows."""
25
+ data = json.loads(text)
26
+ if data.get("format") != BUNDLE_FORMAT:
27
+ raise ValueError(f"Unexpected bundle format: {data.get('format')!r}")
28
+
29
+ rows: list[IntrinsicRecord] = []
30
+ for item in data.get("intrinsics", []):
31
+ metadata = item.get("metadata", {})
32
+ classification = metadata.get("classification", "")
33
+ record_metadata = {"classification": classification}
34
+ for key in ("min_sm", "min_ptx", "min_sm_from", "min_ptx_from"):
35
+ value = str(item.get(key) or metadata.get(key) or "").strip()
36
+ if value:
37
+ record_metadata[key] = value
38
+
39
+ row = IntrinsicRecord(
40
+ name=item["name"],
41
+ signature=item.get("signature", ""),
42
+ description=item.get("description", ""),
43
+ header=item.get("header", ""),
44
+ url=item.get("url", ""),
45
+ architecture="cuda",
46
+ isa=["CUDA"],
47
+ category=item.get("category", ""),
48
+ subcategory=item.get("subcategory", ""),
49
+ instructions=item.get("instructions", []),
50
+ instruction_refs=item.get("instruction_refs", []),
51
+ metadata=record_metadata,
52
+ doc_sections=item.get("doc_sections", {}),
53
+ notes=item.get("notes", []),
54
+ aliases=item.get("aliases", []),
55
+ source="clang-cuda-headers",
56
+ )
57
+ rows.append(row)
58
+
59
+ return rows
60
+
61
+
62
+ def parse_cuda_calls(text: str) -> dict[str, list[str]]:
63
+ """name to call-name list from the bundle call graph.
64
+
65
+ The vendor step records, per header function, the names its body
66
+ calls. This is the graph the doc join and the SM floors walk.
67
+ """
68
+ data = json.loads(text)
69
+ if data.get("format") != BUNDLE_FORMAT:
70
+ raise ValueError(f"Unexpected bundle format: {data.get('format')!r}")
71
+ out: dict[str, list[str]] = {}
72
+ for item in data.get("intrinsics", []):
73
+ out[item["name"]] = [str(name) for name in item.get("calls") or []]
74
+ return out
75
+
76
+
77
+ def _signature_params(signature: str) -> str:
78
+ """The argument list of a signature, its parameter names and types.
79
+
80
+ The math pages carry no Parameters field. The signature does. This
81
+ takes the text between the first "(" and the last ")" of it.
82
+ """
83
+ start = signature.find("(")
84
+ end = signature.rfind(")")
85
+ if start == -1 or end <= start:
86
+ return ""
87
+ args = " ".join(signature[start + 1 : end].split())
88
+ if not args or args == "void":
89
+ return ""
90
+ return args
91
+
92
+
93
+ def _first_sentence(text: str) -> str:
94
+ """The first sentence of a description, with its full stop."""
95
+ joined = " ".join(text.split())
96
+ if not joined:
97
+ return ""
98
+ stop = joined.find(". ")
99
+ if stop == -1:
100
+ return joined
101
+ return joined[: stop + 1]
102
+
103
+
104
+ def _stripped(name: str) -> str:
105
+ """A name as one lowercase run of characters, for containment."""
106
+ return name.replace("_", "").lower()
107
+
108
+
109
+ def _reachable_builtins(name: str, calls: dict[str, list[str]]) -> set[str]:
110
+ """The __nvvm_ and __nv_ tokens the name reaches over the call graph."""
111
+ seen = {name}
112
+ stack = [name]
113
+ out: set[str] = set()
114
+ while stack:
115
+ current = stack.pop()
116
+ for token in calls.get(current, []):
117
+ if token.startswith("__nvvm_") or token.startswith("__nv_"):
118
+ out.add(token)
119
+ elif token in calls and token not in seen:
120
+ seen.add(token)
121
+ stack.append(token)
122
+ return out
123
+
124
+
125
+ def _nearest_documented_caller(name: str, calls: dict[str, list[str]], documented: set[str]) -> str:
126
+ """The nearest documented name that calls this one, else "".
127
+
128
+ Breadth first over the caller side of the call graph. The nearest
129
+ level with a documented name decides. Ties take the shortest name.
130
+ """
131
+ callers: dict[str, list[str]] = {}
132
+ for caller, callees in calls.items():
133
+ for callee in callees:
134
+ callers.setdefault(callee, []).append(caller)
135
+ seen = {name}
136
+ frontier = [name]
137
+ while frontier:
138
+ next_frontier: list[str] = []
139
+ candidates: list[str] = []
140
+ for current in frontier:
141
+ for caller in callers.get(current, []):
142
+ if caller in documented and caller not in candidates:
143
+ candidates.append(caller)
144
+ if caller not in seen:
145
+ seen.add(caller)
146
+ next_frontier.append(caller)
147
+ if candidates:
148
+ return min(candidates, key=lambda item: (len(item), item))
149
+ frontier = next_frontier
150
+ return ""
151
+
152
+
153
+ def _contained_doc_name(
154
+ name: str, calls: dict[str, list[str]], documented: dict[str, tuple[dict, str]]
155
+ ) -> str:
156
+ """A documented name that this name reaches by name containment.
157
+
158
+ Last join tier. The documented name, without underscores, must sit
159
+ inside the row name or inside one of the builtins the row reaches,
160
+ from a word start of that name. The row name is tried first, so a
161
+ helper such as __iAtomicExch maps to atomicExch and not to the type
162
+ cast its body calls. The longest such name wins. This maps the
163
+ header helpers onto the API names the docs use.
164
+ """
165
+ for needles in (
166
+ [name],
167
+ sorted(_reachable_builtins(name, calls)),
168
+ ):
169
+ best = ""
170
+ best_len = 0
171
+ for doc_name, (doc_entry, _source) in sorted(documented.items()):
172
+ # A tier-3 name must be a documented function, not a word of
173
+ # an example or a feature table cell.
174
+ signature = str(doc_entry.get("signature") or "")
175
+ if not declares_name(signature, doc_name):
176
+ continue
177
+ token = _stripped(doc_name)
178
+ if len(token) < 4:
179
+ continue
180
+ if any(_starts_at_word(token, needle) for needle in needles) and len(token) > best_len:
181
+ best = doc_name
182
+ best_len = len(token)
183
+ if best:
184
+ return best
185
+ return ""
186
+
187
+
188
+ def _starts_at_word(token: str, name: str) -> bool:
189
+ """The token matches the name from a word start, underscores aside.
190
+
191
+ Word starts are the name start, the letter after an underscore or
192
+ a digit, and a case hump. Matching skips underscores of the name,
193
+ so a multiword builtin name still matches its word run.
194
+ """
195
+ n = len(name)
196
+ for pos in range(n):
197
+ if pos:
198
+ prev = name[pos - 1]
199
+ hump = name[pos].isupper() and not prev.isupper()
200
+ if prev != "_" and not prev.isdigit() and not hump:
201
+ continue
202
+ i = pos
203
+ ok = True
204
+ for ch in token:
205
+ while i < n and name[i] == "_":
206
+ i += 1
207
+ # A width run in the name, such as the 32 of match32, does
208
+ # not split the word for the doc name.
209
+ if i < n and name[i].isdigit() and name[i].lower() != ch:
210
+ while i < n and (name[i].isdigit() or name[i] == "_"):
211
+ i += 1
212
+ if i >= n or name[i].lower() != ch:
213
+ ok = False
214
+ break
215
+ i += 1
216
+ if ok:
217
+ return True
218
+ return False
219
+
220
+
221
+ def apply_cuda_docs(
222
+ rows: list[IntrinsicRecord], calls: dict[str, list[str]], docs: dict[str, Any]
223
+ ) -> None:
224
+ """Fill documentation fields of the CUDA rows from the parsed pages.
225
+
226
+ Join tiers per name: an exact doc entry, then the nearest documented
227
+ caller over the header call graph, then a documented name contained
228
+ in the row name or in a builtin it reaches. The chosen entry stores
229
+ its caller in metadata["doc_via"] on the fallback tiers. A name with
230
+ no entry keeps no URL and keeps the header signature.
231
+ """
232
+ ulp = docs.get("ulp") or {}
233
+ documented: dict[str, tuple[dict, str]] = {}
234
+ for mapping, source in (
235
+ (docs.get("math") or {}, "cuda-math-api"),
236
+ (docs.get("guide") or {}, "cuda-guide"),
237
+ (docs.get("guide_archive") or {}, "cuda-guide-archive"),
238
+ (docs.get("libdevice") or {}, "libdevice"),
239
+ ):
240
+ for doc_name, entry in mapping.items():
241
+ if doc_name not in documented:
242
+ documented[doc_name] = (entry, source)
243
+ for row in rows:
244
+ name = row.name
245
+ via = ""
246
+ hit = documented.get(name)
247
+ if hit is None:
248
+ via = _nearest_documented_caller(name, calls, set(documented))
249
+ if via:
250
+ hit = documented[via]
251
+ if hit is None:
252
+ via = _contained_doc_name(name, calls, documented)
253
+ if via:
254
+ hit = documented[via]
255
+ if hit is None:
256
+ row.url = ""
257
+ continue
258
+ entry, source = hit
259
+ anchor = str(entry.get("anchor") or "")
260
+ page = str(entry.get("page") or "")
261
+ row.url = resolve_url(page, "#" + anchor) if anchor else resolve_url(page)
262
+ if entry.get("signature"):
263
+ row.signature = str(entry["signature"])
264
+ description = str(entry.get("description") or "")
265
+ summary = str(entry.get("summary") or "")
266
+ row.description = _first_sentence(summary or description)
267
+ sections: dict[str, str] = {}
268
+ if description:
269
+ sections["Description"] = description
270
+ parameters = str(entry.get("parameters") or "")
271
+ if not parameters:
272
+ parameters = _signature_params(str(entry.get("signature") or ""))
273
+ if parameters:
274
+ sections["Parameters"] = parameters
275
+ if entry.get("return_value"):
276
+ sections["Return"] = str(entry["return_value"])
277
+ ulp_entry = ulp.get(name) or {}
278
+ accuracy = str(ulp_entry.get("accuracy") or "")
279
+ if not accuracy:
280
+ accuracy = str(entry.get("accuracy") or "")
281
+ if accuracy:
282
+ readable = tex_to_text(accuracy)
283
+ sections["Accuracy"] = readable
284
+ # The web renders the original TeX later; keep it verbatim.
285
+ if readable != accuracy:
286
+ row.metadata["doc_math"] = accuracy
287
+ row.doc_sections = sections
288
+ if entry.get("accuracy"):
289
+ row.notes = [str(entry["accuracy"])]
290
+ row.metadata["doc_source"] = source
291
+ if via:
292
+ row.metadata["doc_via"] = via
293
+ if entry.get("return_value"):
294
+ row.metadata["result"] = str(entry["return_value"])