blob: e164d335cebba8c403531be39c328ee448d264da [file]
#!/usr/bin/env python3
# Copyright (C) 2023-2026 Free Software Foundation, Inc.
#
# Script to regenerate attr-urls.def from generated HTML.
#
# This file is part of GCC.
#
# GCC is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3. If not see
# <http://www.gnu.org/licenses/>. */
DESCRIPTION = """
Parses the generated HTML (from "make html") to locate anchors
for attributes, and generates a gcc/attr-urls.def file in the source tree,
giving URLs for each attribute, where it can.
Usage (from build/gcc subdirectory):
../../src/gcc/regenerate-attr-urls.py HTML/gcc-15.0.0/ ../../src
To run unit tests:
../../src/gcc/regenerate-attr-urls.py HTML/gcc-15.0.0/ ../../src --unit-test
"""
import argparse
import json
import os
from pathlib import Path
from pprint import pprint
import sys
import re
import unittest
class Index:
def __init__(self):
self.entries = []
self.entries_by_kind = {}
def add_entry(self, url_suffix, name, kind, extra_text, verbose=False):
#if len(self.entries) > 5:
# return
self.entries.append( (url_suffix, name, kind, extra_text) )
if kind in self.entries_by_kind:
by_kind = self.entries_by_kind[kind]
else:
by_kind = []
self.entries_by_kind[kind] = by_kind
by_kind.append( (name, url_suffix, extra_text) )
def parse_attribute_index(self, input_filename, verbose=False):
with open(input_filename) as f:
for line in f:
self.parse_html_line_attribute_index(line, verbose)
def parse_html_line_attribute_index(self, line, verbose=False):
if verbose:
print(repr(line))
# Update for this in the GCC website's bin/preprocess process_html_file:
# | sed -e 's/_002d/-/g' -e 's/_002a/*/g' \
line = line.replace('_002d', '-')
line = line.replace('_002a', '*')
# e.g. <a href="Common-Attributes.html#index-alias"><code class="code">alias</code></a>
# e.g. <a href="PowerPC-Attributes.html#index-altivec_002c-PowerPC"><code class="code">altivec</code>, PowerPC</a>
# Since the unification of the function/variable/type etc attributes
# into a single list, the "kind" no longer appears in the index
# entry text and is always ignored.
m = re.search(r'<a href="([\S]+)"><code[^>]*>([\S]+)</code>([^<]*)</a>', line)
if not m:
return
if verbose:
print(m.groups())
url_suffix, name, extra_text = m.groups()
if extra_text.startswith(', '):
extra_text = extra_text[2:]
# Reject anchors where the name contains a paren (why?)
# e.g. 'target(&quot;3dnowa&quot;)':
if '(' in name:
return
self.add_entry(url_suffix, name, 'all', extra_text)
def generate_file(self, dstpath):
with open(dstpath, 'w') as outf:
self.write_file(outf)
def write_file(self, outf):
outf.write("/* Autogenerated by regenerate-attr-urls.py. */\n\n")
for kind in sorted(self.entries_by_kind.keys()):
by_kind = self.entries_by_kind[kind]
outf.write("const attr_url_entry %s_attrs[] = {\n" % kind)
for name, url_suffix, extra_text in sorted(self.entries_by_kind[kind]):
outf.write(' { "%s", "gcc/%s", "%s", %i},\n'
% (name, url_suffix, extra_text, len(name)))
outf.write("};\n\n")
outf.write('static const struct attr_url_table {\n')
outf.write(' const attr_url_entry *m_table;\n')
outf.write(' const size_t m_table_sz;\n')
outf.write('} attr_url_tables[] = {\n')
for kind in sorted(self.entries_by_kind.keys()):
outf.write(" { %s_attrs, ARRAY_SIZE (%s_attrs) },\n" % (kind, kind))
outf.write("};\n")
INDEX_REL_PATH = 'gcc/Attribute-Index.html'
class TestParsingIndex(unittest.TestCase):
def test_function_attribute(self):
index = Index()
index.parse_html_line_attribute_index('<a href="Common-Attributes.html#index-access"><code class="code">access</code></a>')
self.assertEqual(index.entries, [('Common-Attributes.html#index-access',
'access',
'all',
'')])
def test_function_attribute_with_target(self):
index = Index()
index.parse_html_line_attribute_index('<a href="MIPS-Attributes.html#index-nocompression_002c-MIPS"><code class="code">nocompression</code>, MIPS</a>')
self.assertEqual(index.entries, [('MIPS-Attributes.html#index-nocompression_002c-MIPS',
'nocompression',
'all',
'MIPS')])
def test_reject_parens(self):
index = Index()
index.parse_html_line_attribute_index('<a href="x86-Attributes.html#index-target_0028_00223dnow_0022_0029_002c-x86"><code class="code">target(&quot;3dnow&quot;)</code>, x86</a>')
self.assertEqual(len(index.entries), 0)
def test_type_attribute(self):
index = Index()
index.parse_html_line_attribute_index('<a href="Common-Attributes.html#index-aligned"><code class="code">aligned</code></a>')
self.assertEqual(index.entries, [('Common-Attributes.html#index-aligned',
'aligned',
'all',
'')])
def test_enumerator_attribute(self):
index = Index()
index.parse_html_line_attribute_index('<a href="Common-Attributes.html#index-deprecated"><code class="code">deprecated</code></a>')
self.assertEqual(index.entries, [('Common-Attributes.html#index-deprecated',
'deprecated',
'all',
'')])
def test_label_attribute(self):
index = Index()
index.parse_html_line_attribute_index('<a href="Common-Attributes.html#index-cold"><code class="code">cold</code></a>')
self.assertEqual(index.entries, [('Common-Attributes.html#index-cold',
'cold',
'all',
'')])
def test_statement_attribute(self):
index = Index()
index.parse_html_line_attribute_index('<a href="Common-Attributes.html#index-assume"><code class="code">assume</code></a>')
self.assertEqual(index.entries, [('Common-Attributes.html#index-assume',
'assume',
'all',
'')])
def test_variable_attribute(self):
index = Index()
index.parse_html_line_attribute_index('<a href="AVR-Attributes.html#index-absdata_002c-AVR"><code class="code">absdata</code>, AVR</a>')
self.assertEqual(index.entries, [('AVR-Attributes.html#index-absdata_002c-AVR',
'absdata',
'all',
'AVR')])
# Warning, this is fragile and depends on these being the first two
# entries in the HTML index. Adding or removing options may break this!
def test_parse_attribute_index(self):
index = Index()
index.parse_attribute_index(INPUT_HTML_PATH / INDEX_REL_PATH)
self.assertEqual(index.entries_by_kind['all'][0],
('abi_tag',
'C_002b_002b-Attributes.html#index-abi_005ftag',
''))
self.assertEqual(index.entries_by_kind['all'][1],
('absdata',
'AVR-Attributes.html#index-absdata_002c-AVR',
'AVR'))
def main(args):
index = Index()
index.parse_attribute_index(args.base_html_dir / INDEX_REL_PATH)
dstpath = args.src_gcc_dir / 'gcc' / 'attr-urls.def'
index.generate_file(dstpath)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=DESCRIPTION,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('base_html_dir', type=Path)
parser.add_argument('src_gcc_dir', type=Path)
parser.add_argument('--unit-test', action='store_true')
args = parser.parse_args()
if args.unit_test:
INPUT_HTML_PATH = args.base_html_dir
unittest.main(argv=[sys.argv[0], '-v'])
else:
main(args)