1778b8ebeSJonathan Corbet#!/usr/bin/env python3 2778b8ebeSJonathan Corbet# xxpylint: disable=R0903 3778b8ebeSJonathan Corbet# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. 4778b8ebeSJonathan Corbet# SPDX-License-Identifier: GPL-2.0 5778b8ebeSJonathan Corbet 6778b8ebeSJonathan Corbet""" 7778b8ebeSJonathan CorbetConvert ABI what into regular expressions 8778b8ebeSJonathan Corbet""" 9778b8ebeSJonathan Corbet 10778b8ebeSJonathan Corbetimport re 11778b8ebeSJonathan Corbetimport sys 12778b8ebeSJonathan Corbet 13778b8ebeSJonathan Corbetfrom pprint import pformat 14778b8ebeSJonathan Corbet 15*992a9df4SJonathan Corbetfrom abi.abi_parser import AbiParser 16*992a9df4SJonathan Corbetfrom abi.helpers import AbiDebug 17778b8ebeSJonathan Corbet 18778b8ebeSJonathan Corbetclass AbiRegex(AbiParser): 19778b8ebeSJonathan Corbet """Extends AbiParser to search ABI nodes with regular expressions""" 20778b8ebeSJonathan Corbet 21778b8ebeSJonathan Corbet # Escape only ASCII visible characters 22778b8ebeSJonathan Corbet escape_symbols = r"([\x21-\x29\x2b-\x2d\x3a-\x40\x5c\x60\x7b-\x7e])" 23778b8ebeSJonathan Corbet leave_others = "others" 24778b8ebeSJonathan Corbet 25778b8ebeSJonathan Corbet # Tuples with regular expressions to be compiled and replacement data 26778b8ebeSJonathan Corbet re_whats = [ 27778b8ebeSJonathan Corbet # Drop escape characters that might exist 28778b8ebeSJonathan Corbet (re.compile("\\\\"), ""), 29778b8ebeSJonathan Corbet 30778b8ebeSJonathan Corbet # Temporarily escape dot characters 31778b8ebeSJonathan Corbet (re.compile(r"\."), "\xf6"), 32778b8ebeSJonathan Corbet 33778b8ebeSJonathan Corbet # Temporarily change [0-9]+ type of patterns 34778b8ebeSJonathan Corbet (re.compile(r"\[0\-9\]\+"), "\xff"), 35778b8ebeSJonathan Corbet 36778b8ebeSJonathan Corbet # Temporarily change [\d+-\d+] type of patterns 37778b8ebeSJonathan Corbet (re.compile(r"\[0\-\d+\]"), "\xff"), 38778b8ebeSJonathan Corbet (re.compile(r"\[0:\d+\]"), "\xff"), 39778b8ebeSJonathan Corbet (re.compile(r"\[(\d+)\]"), "\xf4\\\\d+\xf5"), 40778b8ebeSJonathan Corbet 41778b8ebeSJonathan Corbet # Temporarily change [0-9] type of patterns 42778b8ebeSJonathan Corbet (re.compile(r"\[(\d)\-(\d)\]"), "\xf4\1-\2\xf5"), 43778b8ebeSJonathan Corbet 44778b8ebeSJonathan Corbet # Handle multiple option patterns 45778b8ebeSJonathan Corbet (re.compile(r"[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]"), r"(\1|\2)"), 46778b8ebeSJonathan Corbet 47778b8ebeSJonathan Corbet # Handle wildcards 48778b8ebeSJonathan Corbet (re.compile(r"([^\/])\*"), "\\1\\\\w\xf7"), 49778b8ebeSJonathan Corbet (re.compile(r"/\*/"), "/.*/"), 50778b8ebeSJonathan Corbet (re.compile(r"/\xf6\xf6\xf6"), "/.*"), 51778b8ebeSJonathan Corbet (re.compile(r"\<[^\>]+\>"), "\\\\w\xf7"), 52778b8ebeSJonathan Corbet (re.compile(r"\{[^\}]+\}"), "\\\\w\xf7"), 53778b8ebeSJonathan Corbet (re.compile(r"\[[^\]]+\]"), "\\\\w\xf7"), 54778b8ebeSJonathan Corbet 55778b8ebeSJonathan Corbet (re.compile(r"XX+"), "\\\\w\xf7"), 56778b8ebeSJonathan Corbet (re.compile(r"([^A-Z])[XYZ]([^A-Z])"), "\\1\\\\w\xf7\\2"), 57778b8ebeSJonathan Corbet (re.compile(r"([^A-Z])[XYZ]$"), "\\1\\\\w\xf7"), 58778b8ebeSJonathan Corbet (re.compile(r"_[AB]_"), "_\\\\w\xf7_"), 59778b8ebeSJonathan Corbet 60778b8ebeSJonathan Corbet # Recover [0-9] type of patterns 61778b8ebeSJonathan Corbet (re.compile(r"\xf4"), "["), 62778b8ebeSJonathan Corbet (re.compile(r"\xf5"), "]"), 63778b8ebeSJonathan Corbet 64778b8ebeSJonathan Corbet # Remove duplicated spaces 65778b8ebeSJonathan Corbet (re.compile(r"\s+"), r" "), 66778b8ebeSJonathan Corbet 67778b8ebeSJonathan Corbet # Special case: drop comparison as in: 68778b8ebeSJonathan Corbet # What: foo = <something> 69778b8ebeSJonathan Corbet # (this happens on a few IIO definitions) 70778b8ebeSJonathan Corbet (re.compile(r"\s*\=.*$"), ""), 71778b8ebeSJonathan Corbet 72778b8ebeSJonathan Corbet # Escape all other symbols 73778b8ebeSJonathan Corbet (re.compile(escape_symbols), r"\\\1"), 74778b8ebeSJonathan Corbet (re.compile(r"\\\\"), r"\\"), 75778b8ebeSJonathan Corbet (re.compile(r"\\([\[\]\(\)\|])"), r"\1"), 76778b8ebeSJonathan Corbet (re.compile(r"(\d+)\\(-\d+)"), r"\1\2"), 77778b8ebeSJonathan Corbet 78778b8ebeSJonathan Corbet (re.compile(r"\xff"), r"\\d+"), 79778b8ebeSJonathan Corbet 80778b8ebeSJonathan Corbet # Special case: IIO ABI which a parenthesis. 81778b8ebeSJonathan Corbet (re.compile(r"sqrt(.*)"), r"sqrt(.*)"), 82778b8ebeSJonathan Corbet 83778b8ebeSJonathan Corbet # Simplify regexes with multiple .* 84778b8ebeSJonathan Corbet (re.compile(r"(?:\.\*){2,}"), ""), 85778b8ebeSJonathan Corbet 86778b8ebeSJonathan Corbet # Recover dot characters 87778b8ebeSJonathan Corbet (re.compile(r"\xf6"), "\\."), 88778b8ebeSJonathan Corbet # Recover plus characters 89778b8ebeSJonathan Corbet (re.compile(r"\xf7"), "+"), 90778b8ebeSJonathan Corbet ] 91778b8ebeSJonathan Corbet re_has_num = re.compile(r"\\d") 92778b8ebeSJonathan Corbet 93778b8ebeSJonathan Corbet # Symbol name after escape_chars that are considered a devnode basename 94778b8ebeSJonathan Corbet re_symbol_name = re.compile(r"(\w|\\[\.\-\:])+$") 95778b8ebeSJonathan Corbet 96778b8ebeSJonathan Corbet # List of popular group names to be skipped to minimize regex group size 97778b8ebeSJonathan Corbet # Use AbiDebug.SUBGROUP_SIZE to detect those 98778b8ebeSJonathan Corbet skip_names = set(["devices", "hwmon"]) 99778b8ebeSJonathan Corbet 100778b8ebeSJonathan Corbet def regex_append(self, what, new): 101778b8ebeSJonathan Corbet """ 102778b8ebeSJonathan Corbet Get a search group for a subset of regular expressions. 103778b8ebeSJonathan Corbet 104778b8ebeSJonathan Corbet As ABI may have thousands of symbols, using a for to search all 105778b8ebeSJonathan Corbet regular expressions is at least O(n^2). When there are wildcards, 106778b8ebeSJonathan Corbet the complexity increases substantially, eventually becoming exponential. 107778b8ebeSJonathan Corbet 108778b8ebeSJonathan Corbet To avoid spending too much time on them, use a logic to split 109778b8ebeSJonathan Corbet them into groups. The smaller the group, the better, as it would 110778b8ebeSJonathan Corbet mean that searches will be confined to a small number of regular 111778b8ebeSJonathan Corbet expressions. 112778b8ebeSJonathan Corbet 113778b8ebeSJonathan Corbet The conversion to a regex subset is tricky, as we need something 114778b8ebeSJonathan Corbet that can be easily obtained from the sysfs symbol and from the 115778b8ebeSJonathan Corbet regular expression. So, we need to discard nodes that have 116778b8ebeSJonathan Corbet wildcards. 117778b8ebeSJonathan Corbet 118778b8ebeSJonathan Corbet If it can't obtain a subgroup, place the regular expression inside 119778b8ebeSJonathan Corbet a special group (self.leave_others). 120778b8ebeSJonathan Corbet """ 121778b8ebeSJonathan Corbet 122778b8ebeSJonathan Corbet search_group = None 123778b8ebeSJonathan Corbet 124778b8ebeSJonathan Corbet for search_group in reversed(new.split("/")): 125778b8ebeSJonathan Corbet if not search_group or search_group in self.skip_names: 126778b8ebeSJonathan Corbet continue 127778b8ebeSJonathan Corbet if self.re_symbol_name.match(search_group): 128778b8ebeSJonathan Corbet break 129778b8ebeSJonathan Corbet 130778b8ebeSJonathan Corbet if not search_group: 131778b8ebeSJonathan Corbet search_group = self.leave_others 132778b8ebeSJonathan Corbet 133778b8ebeSJonathan Corbet if self.debug & AbiDebug.SUBGROUP_MAP: 134778b8ebeSJonathan Corbet self.log.debug("%s: mapped as %s", what, search_group) 135778b8ebeSJonathan Corbet 136778b8ebeSJonathan Corbet try: 137778b8ebeSJonathan Corbet if search_group not in self.regex_group: 138778b8ebeSJonathan Corbet self.regex_group[search_group] = [] 139778b8ebeSJonathan Corbet 140778b8ebeSJonathan Corbet self.regex_group[search_group].append(re.compile(new)) 141778b8ebeSJonathan Corbet if self.search_string: 142778b8ebeSJonathan Corbet if what.find(self.search_string) >= 0: 143778b8ebeSJonathan Corbet print(f"What: {what}") 144778b8ebeSJonathan Corbet except re.PatternError: 145778b8ebeSJonathan Corbet self.log.warning("Ignoring '%s' as it produced an invalid regex:\n" 146778b8ebeSJonathan Corbet " '%s'", what, new) 147778b8ebeSJonathan Corbet 148778b8ebeSJonathan Corbet def get_regexes(self, what): 149778b8ebeSJonathan Corbet """ 150778b8ebeSJonathan Corbet Given an ABI devnode, return a list of all regular expressions that 151778b8ebeSJonathan Corbet may match it, based on the sub-groups created by regex_append() 152778b8ebeSJonathan Corbet """ 153778b8ebeSJonathan Corbet 154778b8ebeSJonathan Corbet re_list = [] 155778b8ebeSJonathan Corbet 156778b8ebeSJonathan Corbet patches = what.split("/") 157778b8ebeSJonathan Corbet patches.reverse() 158778b8ebeSJonathan Corbet patches.append(self.leave_others) 159778b8ebeSJonathan Corbet 160778b8ebeSJonathan Corbet for search_group in patches: 161778b8ebeSJonathan Corbet if search_group in self.regex_group: 162778b8ebeSJonathan Corbet re_list += self.regex_group[search_group] 163778b8ebeSJonathan Corbet 164778b8ebeSJonathan Corbet return re_list 165778b8ebeSJonathan Corbet 166778b8ebeSJonathan Corbet def __init__(self, *args, **kwargs): 167778b8ebeSJonathan Corbet """ 168778b8ebeSJonathan Corbet Override init method to get verbose argument 169778b8ebeSJonathan Corbet """ 170778b8ebeSJonathan Corbet 171778b8ebeSJonathan Corbet self.regex_group = None 172778b8ebeSJonathan Corbet self.search_string = None 173778b8ebeSJonathan Corbet self.re_string = None 174778b8ebeSJonathan Corbet 175778b8ebeSJonathan Corbet if "search_string" in kwargs: 176778b8ebeSJonathan Corbet self.search_string = kwargs.get("search_string") 177778b8ebeSJonathan Corbet del kwargs["search_string"] 178778b8ebeSJonathan Corbet 179778b8ebeSJonathan Corbet if self.search_string: 180778b8ebeSJonathan Corbet 181778b8ebeSJonathan Corbet try: 182778b8ebeSJonathan Corbet self.re_string = re.compile(self.search_string) 183778b8ebeSJonathan Corbet except re.PatternError as e: 184778b8ebeSJonathan Corbet msg = f"{self.search_string} is not a valid regular expression" 185778b8ebeSJonathan Corbet raise ValueError(msg) from e 186778b8ebeSJonathan Corbet 187778b8ebeSJonathan Corbet super().__init__(*args, **kwargs) 188778b8ebeSJonathan Corbet 189778b8ebeSJonathan Corbet def parse_abi(self, *args, **kwargs): 190778b8ebeSJonathan Corbet 191778b8ebeSJonathan Corbet super().parse_abi(*args, **kwargs) 192778b8ebeSJonathan Corbet 193778b8ebeSJonathan Corbet self.regex_group = {} 194778b8ebeSJonathan Corbet 195778b8ebeSJonathan Corbet print("Converting ABI What fields into regexes...", file=sys.stderr) 196778b8ebeSJonathan Corbet 197778b8ebeSJonathan Corbet for t in sorted(self.data.items(), key=lambda x: x[0]): 198778b8ebeSJonathan Corbet v = t[1] 199778b8ebeSJonathan Corbet if v.get("type") == "File": 200778b8ebeSJonathan Corbet continue 201778b8ebeSJonathan Corbet 202778b8ebeSJonathan Corbet v["regex"] = [] 203778b8ebeSJonathan Corbet 204778b8ebeSJonathan Corbet for what in v.get("what", []): 205778b8ebeSJonathan Corbet if not what.startswith("/sys"): 206778b8ebeSJonathan Corbet continue 207778b8ebeSJonathan Corbet 208778b8ebeSJonathan Corbet new = what 209778b8ebeSJonathan Corbet for r, s in self.re_whats: 210778b8ebeSJonathan Corbet try: 211778b8ebeSJonathan Corbet new = r.sub(s, new) 212778b8ebeSJonathan Corbet except re.PatternError as e: 213778b8ebeSJonathan Corbet # Help debugging troubles with new regexes 214778b8ebeSJonathan Corbet raise re.PatternError(f"{e}\nwhile re.sub('{r.pattern}', {s}, str)") from e 215778b8ebeSJonathan Corbet 216778b8ebeSJonathan Corbet v["regex"].append(new) 217778b8ebeSJonathan Corbet 218778b8ebeSJonathan Corbet if self.debug & AbiDebug.REGEX: 219778b8ebeSJonathan Corbet self.log.debug("%-90s <== %s", new, what) 220778b8ebeSJonathan Corbet 221778b8ebeSJonathan Corbet # Store regex into a subgroup to speedup searches 222778b8ebeSJonathan Corbet self.regex_append(what, new) 223778b8ebeSJonathan Corbet 224778b8ebeSJonathan Corbet if self.debug & AbiDebug.SUBGROUP_DICT: 225778b8ebeSJonathan Corbet self.log.debug("%s", pformat(self.regex_group)) 226778b8ebeSJonathan Corbet 227778b8ebeSJonathan Corbet if self.debug & AbiDebug.SUBGROUP_SIZE: 228778b8ebeSJonathan Corbet biggestd_keys = sorted(self.regex_group.keys(), 229778b8ebeSJonathan Corbet key= lambda k: len(self.regex_group[k]), 230778b8ebeSJonathan Corbet reverse=True) 231778b8ebeSJonathan Corbet 232778b8ebeSJonathan Corbet print("Top regex subgroups:", file=sys.stderr) 233778b8ebeSJonathan Corbet for k in biggestd_keys[:10]: 234778b8ebeSJonathan Corbet print(f"{k} has {len(self.regex_group[k])} elements", file=sys.stderr) 235