xref: /linux/tools/lib/python/abi/abi_regex.py (revision f96163865a1346b199cc38e827269296f0f24ab0)
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