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.
- {simdref-0.0.20/src/simdref.egg-info → simdref-0.0.21}/PKG-INFO +1 -1
- {simdref-0.0.20 → simdref-0.0.21}/pyproject.toml +1 -1
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/cuda.py +98 -7
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/cuda_docs.py +213 -35
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/display.py +281 -49
- simdref-0.0.21/src/simdref/docblocks.py +803 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/export.py +14 -2
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ingest_catalog.py +2 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/models.py +4 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ptx.py +60 -14
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/tui.py +52 -34
- {simdref-0.0.20 → simdref-0.0.21/src/simdref.egg-info}/PKG-INFO +1 -1
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/SOURCES.txt +8 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_declaration_parser.py +40 -2
- simdref-0.0.21/tests/test_cuda_doc_blocks.py +121 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_docs_guide.py +26 -4
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_docs_math.py +91 -11
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_join.py +75 -3
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_detail_metadata.py +2 -1
- simdref-0.0.21/tests/test_doc_entry_merge.py +101 -0
- simdref-0.0.21/tests/test_export_site_data.py +109 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_lsp_web.py +13 -7
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_ptx.py +32 -0
- simdref-0.0.21/tests/test_section_order.py +97 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_tui.py +28 -10
- simdref-0.0.21/tests/test_tui_doc_blocks.py +424 -0
- simdref-0.0.21/tests/test_tui_doc_fixes.py +182 -0
- simdref-0.0.21/tests/test_tui_doc_fmt.py +373 -0
- {simdref-0.0.20 → simdref-0.0.21}/LICENSE +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/README.md +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/setup.cfg +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/__init__.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/__main__.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/annotate.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/arm_instructions.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/asm_identity.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/asmline.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/catkey.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/cli.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/filters.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/htmltext.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ingest.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ingest_pdf.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ingest_sources.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/lsp.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/manpages.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/operands.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/__init__.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/base.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/intel.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/registry.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfparse/types.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/pdfrefs.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/__init__.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/amd_swopt.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/arm_swopt.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/cores.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/instlatx64.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/intel_guide.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/intel_optref.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/llvm_mca.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/perf_sources/merge.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/queries.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/regtable.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/riscv.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/search.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/storage.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/tablecols.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/ui_labels.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref/urls.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/dependency_links.txt +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/entry_points.txt +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/requires.txt +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/src/simdref.egg-info/top_level.txt +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_a1_sdm_parser.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_a2_perf_rule.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_a4_arm_pdfrefs.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_a5_offline_aarchmrs.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_amd_match_llvm_mc.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_annotate.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_annotate_microarch.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_annotate_positions.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_annotate_scans.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_arm_arch_filter.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_arrangement_tail.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_asm_identity.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_asm_wire.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_asmline.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_audit_coverage.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_auto_update_on_version_change.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_b1_latency_pairs.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_b2_metadata.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_b3_sdm_forms.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_b4_descriptions_provenance.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_b5_linking.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_b5_perf_family.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_b5_sme.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_c0_perf_priority.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_category_filter.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_catkey.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_bare_query.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_bootstrap_progress.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_help.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_llm.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cli_perf_line.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_coverage_live.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_coverage_parity.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_floors.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_intrinsics.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_links.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_td_expansion.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_tex.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_cuda_wiring.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_display.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_display_scanners.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_empty_query_state.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_export.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_export_sm.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_export_sm_column.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_export_viz.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_fetch_retry.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_filters.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_fixui_display.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_htmltext.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_ingest_enc_fields.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_ingest_sources.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_int_arm_isa_label.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_int_core_ids.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_int_ingest_registration.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_int_sdm_family_fallback.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_intel_operation_and_url.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_isa_detect_llvm_mc.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue2_fixes.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_51.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_52.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_53_54.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_55.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_56_57a.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_57_cpi.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_60_c1.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_60_contract.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_60_crossarch.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_60_matcher.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_arm_alias.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_branch_target.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_compressed.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_foreign_arch.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_identity.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_m1.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_m5.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_perfdata.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_sme_za.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_text.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issue_65_timing.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issues_23_24_25.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_issues_51_57_join.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_arm_form_pick.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_arm_postindex.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_att_movx.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_autoupdate.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_catalog_gaps.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_ingest_isa_merge.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_intel_size.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_perf_join.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_leftover_riscv_shared.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_lsp_cuda.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_lsp_ptx.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_lsp_stdio.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_models.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_no_catalog_row.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_objdump_listings.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_operands.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_pdfparse.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_kind_toggle.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_amd_swopt.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_arm_swopt.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_instlatx64.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_intel_guide.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_source_intel_optref.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_perf_sources.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_presentation.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_preset_chip_sync.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_preset_persistence.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_regtable.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_related_entries.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_release_tag_pin.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_result_row_badges.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_riscv_docs_pages.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_search.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_search_pushdown.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_shared_source_helpers.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_sm_filter.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_source_kind_filter.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_source_validation.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_storage_alnum.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_storage_payload.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_storage_schema.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_tablecols.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_ui_labels_parity.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_update_build_stamp.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_urls.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_vendor_riscv_categories.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_vendor_riscv_sources.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_version_matches_pyproject.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_x86_key_widths.py +0 -0
- {simdref-0.0.20 → simdref-0.0.21}/tests/test_x86_linking.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simdref"
|
|
7
|
-
version = "0.0.
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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
|
|
853
|
+
"""The text declares the name: a return type precedes its call.
|
|
704
854
|
|
|
705
|
-
A declaration has a type
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
710
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
927
|
-
acc =
|
|
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(
|
|
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(
|
|
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
|
|