tools: quit the super-linear regex scans in the spec table extractors (SonarCloud S8786) (#3007)

* packaging: use [[ for bash conditional tests (SonarCloud S7688)

SonarCloud flags every bare [ conditional in bash scripts under packaging/ as a reliability smell (rule S7688). Convert the flagged [ ... ] tests to [[ ... ]]: safer word-splitting and no glob surprises, same semantics for these string and file tests.

* tools: quit the super-linear regex scans in the spec table extractors (SonarCloud S8786)

SonarCloud flags five regular expressions in the AV1/HEVC extract_tables.py tools as super-linear: a lazy (.*) under re.S that rescans the whole remaining spec text at every position, a \s*([^|]+?)\s* sandwich, and \s+(.*) tails that can absorb the same whitespace repeatedly.

- the fence block reader now walks the ~~~~ c / tilde-run pairs with two linear searches instead of the lazy .*? findall; block-pair and 7+ tilde-run semantics are unchanged
- the symbol-row capture restricts its first and last characters away from [|] so the lazy group's backtracking jumps are gone (identical for every non-blank cell; a cell of only spaces is no longer captured, which the table rows never contain)
- the empty-comma cell splitter no longer lets \s* swallow the newline it anchors on; consecutive line breaks inside a cell no longer split (spec cells are single-line rows)
- initValue / ScalingList value captures start on a non-space so \\s+ and .* no longer share a split point (captured text is unchanged for all real values)

Verified old vs new run on synthetic spec markdown: byte-identical spé tables output and stderr.
This commit is contained in:
Dylan M. Taylor
2026-10-23 13:08:41 -04:00
committed by GitHub
parent 4d089668a8
commit 20e159dd68
2 changed files with 20 additions and 5 deletions
+18 -3
View File
@@ -49,7 +49,7 @@ def ev(expr):
# --- constants: symbol table (section 3) ---
pending = []
for line in texts["03.symbols.md"].split("\n"):
m = re.match(r"^\|\s*`([A-Z][A-Z0-9_]*)`\s*\|\s*([^|]+?)\s*\|", line)
m = re.match(r"^\|\s*`([A-Z][A-Z0-9_]*)`\s*\|\s*([^\s|](?:[^|]*[^|\s])?)\s*\|", line)
if m:
pending.append((m.group(1), m.group(2)))
# --- constants: value/name tables (section 6 semantics, section 8/9) ---
@@ -91,7 +91,7 @@ for name, val in pending:
def cells(text):
"""Split one initialiser cell. A missing comma between two operands on separate lines
(Split_Tx_Size in the spec text: `TX_32X32` / `TX_4X8`) yields two cells."""
parts = re.split(r"(?<=[A-Za-z0-9_])\s*\n\s*(?=[A-Za-z0-9_])", text.strip())
parts = re.split(r"(?<=[A-Za-z0-9_])[^\S\n]*\n[^\S\n]*(?=[A-Za-z0-9_])", text.strip())
if len(parts) > 1:
sys.stderr.write("note: missing comma in spec table between %s\n" % parts)
return parts
@@ -132,10 +132,25 @@ def shape(x):
return []
# `~~~~` opens a block on its own line (with an optional `c` marker) and the next run of
# tildes closes it. Walk the pairs with two linear searches instead of the lazy `(.*?)`
# with re.S, which rescans the whole remaining text at every position (super-linear).
FENCE_OPEN = re.compile(r"~~~~~?\s*c?\n")
FENCE_CLOSE = re.compile(r"~~~~~?")
tables = {}
for f in files:
t = texts[f]
for block in re.findall(r"~~~~~?\s*c?\n(.*?)~~~~~?", t, re.S):
pos = 0
while True:
m = FENCE_OPEN.search(t, pos)
if not m:
break
c = FENCE_CLOSE.search(t, m.end())
if not c:
break
block = t[m.end():c.start()]
pos = c.end()
b = re.sub(r"//[^\n]*", "", block)
b = re.sub(r"/\*.*?\*/", "", b, flags=re.S)
for m in re.finditer(r"(?m)^\s*([A-Z][A-Za-z0-9_]*)\s*((?:\[[^\]=]*\]\s*)+)=\s*", b):
+2 -2
View File
@@ -44,7 +44,7 @@ def body_after(title_regex, stop_regex=r"^\s*(Table \d+-\d+ - |\d+\s+Rec\. ITU|R
def init_values(table):
vals = []
for l in body_after(r"Table %s - Values of initValue" % re.escape(table)):
m = re.match(r"^\s*initValue\s+(.*)$", l)
m = re.match(r"^\s*initValue\s+(\S.*)$", l)
if m:
vals += [int(v) for v in m.group(1).split()]
return vals
@@ -118,7 +118,7 @@ w("")
# Table 7-6: default 8x8 scaling lists (intra matrixId 0..2, inter 3..5)
intra, inter = [], []
for l in body_after(r"Table 7-6 - Specification of default values of ScalingList"):
m = re.match(r"^\s*ScalingList\[ 1\.\.3 \]\[ (0\.\.2|3\.\.5) \]\[ i \]\s+(.*)$", l)
m = re.match(r"^\s*ScalingList\[ 1\.\.3 \]\[ (0\.\.2|3\.\.5) \]\[ i \]\s+(\S.*)$", l)
if m:
(intra if m.group(1) == "0..2" else inter).extend(int(v) for v in m.group(2).split())
assert len(intra) == 64 and len(inter) == 64