| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- #!/usr/bin/env python3
- # xxpylint: disable=R0903
- # Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
- # SPDX-License-Identifier: GPL-2.0
- """
- Convert ABI what into regular expressions
- """
- import re
- import sys
- from pprint import pformat
- from abi.abi_parser import AbiParser
- from abi.helpers import AbiDebug
- class AbiRegex(AbiParser):
- """
- Extends AbiParser to search ABI nodes with regular expressions.
- There some optimizations here to allow a quick symbol search:
- instead of trying to place all symbols altogether an doing linear
- search which is very time consuming, create a tree with one depth,
- grouping similar symbols altogether.
- Yet, sometimes a full search will be needed, so we have a special branch
- on such group tree where other symbols are placed.
- """
- #: Escape only ASCII visible characters.
- escape_symbols = r"([\x21-\x29\x2b-\x2d\x3a-\x40\x5c\x60\x7b-\x7e])"
- #: Special group for other nodes.
- leave_others = "others"
- # Tuples with regular expressions to be compiled and replacement data
- re_whats = [
- # Drop escape characters that might exist
- (re.compile("\\\\"), ""),
- # Temporarily escape dot characters
- (re.compile(r"\."), "\xf6"),
- # Temporarily change [0-9]+ type of patterns
- (re.compile(r"\[0\-9\]\+"), "\xff"),
- # Temporarily change [\d+-\d+] type of patterns
- (re.compile(r"\[0\-\d+\]"), "\xff"),
- (re.compile(r"\[0:\d+\]"), "\xff"),
- (re.compile(r"\[(\d+)\]"), "\xf4\\\\d+\xf5"),
- # Temporarily change [0-9] type of patterns
- (re.compile(r"\[(\d)\-(\d)\]"), "\xf4\1-\2\xf5"),
- # Handle multiple option patterns
- (re.compile(r"[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]"), r"(\1|\2)"),
- # Handle wildcards
- (re.compile(r"([^\/])\*"), "\\1\\\\w\xf7"),
- (re.compile(r"/\*/"), "/.*/"),
- (re.compile(r"/\xf6\xf6\xf6"), "/.*"),
- (re.compile(r"\<[^\>]+\>"), "\\\\w\xf7"),
- (re.compile(r"\{[^\}]+\}"), "\\\\w\xf7"),
- (re.compile(r"\[[^\]]+\]"), "\\\\w\xf7"),
- (re.compile(r"XX+"), "\\\\w\xf7"),
- (re.compile(r"([^A-Z])[XYZ]([^A-Z])"), "\\1\\\\w\xf7\\2"),
- (re.compile(r"([^A-Z])[XYZ]$"), "\\1\\\\w\xf7"),
- (re.compile(r"_[AB]_"), "_\\\\w\xf7_"),
- # Recover [0-9] type of patterns
- (re.compile(r"\xf4"), "["),
- (re.compile(r"\xf5"), "]"),
- # Remove duplicated spaces
- (re.compile(r"\s+"), r" "),
- # Special case: drop comparison as in:
- # What: foo = <something>
- # (this happens on a few IIO definitions)
- (re.compile(r"\s*\=.*$"), ""),
- # Escape all other symbols
- (re.compile(escape_symbols), r"\\\1"),
- (re.compile(r"\\\\"), r"\\"),
- (re.compile(r"\\([\[\]\(\)\|])"), r"\1"),
- (re.compile(r"(\d+)\\(-\d+)"), r"\1\2"),
- (re.compile(r"\xff"), r"\\d+"),
- # Special case: IIO ABI which a parenthesis.
- (re.compile(r"sqrt(.*)"), r"sqrt(.*)"),
- # Simplify regexes with multiple .*
- (re.compile(r"(?:\.\*){2,}"), ""),
- # Recover dot characters
- (re.compile(r"\xf6"), "\\."),
- # Recover plus characters
- (re.compile(r"\xf7"), "+"),
- ]
- #: Regex to check if the symbol name has a number on it.
- re_has_num = re.compile(r"\\d")
- #: Symbol name after escape_chars that are considered a devnode basename.
- re_symbol_name = re.compile(r"(\w|\\[\.\-\:])+$")
- #: List of popular group names to be skipped to minimize regex group size
- #: Use AbiDebug.SUBGROUP_SIZE to detect those.
- skip_names = set(["devices", "hwmon"])
- def regex_append(self, what, new):
- """
- Get a search group for a subset of regular expressions.
- As ABI may have thousands of symbols, using a for to search all
- regular expressions is at least O(n^2). When there are wildcards,
- the complexity increases substantially, eventually becoming exponential.
- To avoid spending too much time on them, use a logic to split
- them into groups. The smaller the group, the better, as it would
- mean that searches will be confined to a small number of regular
- expressions.
- The conversion to a regex subset is tricky, as we need something
- that can be easily obtained from the sysfs symbol and from the
- regular expression. So, we need to discard nodes that have
- wildcards.
- If it can't obtain a subgroup, place the regular expression inside
- a special group (self.leave_others).
- """
- search_group = None
- for search_group in reversed(new.split("/")):
- if not search_group or search_group in self.skip_names:
- continue
- if self.re_symbol_name.match(search_group):
- break
- if not search_group:
- search_group = self.leave_others
- if self.debug & AbiDebug.SUBGROUP_MAP:
- self.log.debug("%s: mapped as %s", what, search_group)
- try:
- if search_group not in self.regex_group:
- self.regex_group[search_group] = []
- self.regex_group[search_group].append(re.compile(new))
- if self.search_string:
- if what.find(self.search_string) >= 0:
- print(f"What: {what}")
- except re.PatternError:
- self.log.warning("Ignoring '%s' as it produced an invalid regex:\n"
- " '%s'", what, new)
- def get_regexes(self, what):
- """
- Given an ABI devnode, return a list of all regular expressions that
- may match it, based on the sub-groups created by regex_append().
- """
- re_list = []
- patches = what.split("/")
- patches.reverse()
- patches.append(self.leave_others)
- for search_group in patches:
- if search_group in self.regex_group:
- re_list += self.regex_group[search_group]
- return re_list
- def __init__(self, *args, **kwargs):
- """
- Override init method to get verbose argument
- """
- self.regex_group = None
- self.search_string = None
- self.re_string = None
- if "search_string" in kwargs:
- self.search_string = kwargs.get("search_string")
- del kwargs["search_string"]
- if self.search_string:
- try:
- self.re_string = re.compile(self.search_string)
- except re.PatternError as e:
- msg = f"{self.search_string} is not a valid regular expression"
- raise ValueError(msg) from e
- super().__init__(*args, **kwargs)
- def parse_abi(self, *args, **kwargs):
- super().parse_abi(*args, **kwargs)
- self.regex_group = {}
- print("Converting ABI What fields into regexes...", file=sys.stderr)
- for t in sorted(self.data.items(), key=lambda x: x[0]):
- v = t[1]
- if v.get("type") == "File":
- continue
- v["regex"] = []
- for what in v.get("what", []):
- if not what.startswith("/sys"):
- continue
- new = what
- for r, s in self.re_whats:
- try:
- new = r.sub(s, new)
- except re.PatternError as e:
- # Help debugging troubles with new regexes
- raise re.PatternError(f"{e}\nwhile re.sub('{r.pattern}', {s}, str)") from e
- v["regex"].append(new)
- if self.debug & AbiDebug.REGEX:
- self.log.debug("%-90s <== %s", new, what)
- # Store regex into a subgroup to speedup searches
- self.regex_append(what, new)
- if self.debug & AbiDebug.SUBGROUP_DICT:
- self.log.debug("%s", pformat(self.regex_group))
- if self.debug & AbiDebug.SUBGROUP_SIZE:
- biggestd_keys = sorted(self.regex_group.keys(),
- key= lambda k: len(self.regex_group[k]),
- reverse=True)
- print("Top regex subgroups:", file=sys.stderr)
- for k in biggestd_keys[:10]:
- print(f"{k} has {len(self.regex_group[k])} elements", file=sys.stderr)
|