diff --git a/cmake/match.py b/cmake/match.py index 8445f1c558..2886eec5a8 100755 --- a/cmake/match.py +++ b/cmake/match.py @@ -5,77 +5,138 @@ # See LICENSE.TXT # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# check if all lines match in a file -# lines in a match file can contain regex inside of double curly braces {{}} +# Check if all input file content matches match file content. +# Lines in a match file can contain regex inside of double curly braces {{}}. +# Regex patterns are limited to single line. +# +# List of available special tags: +# {{OPT}} - makes content in the same line as the tag optional +# {{IGNORE}} - ignores all content until next match or end of input file +# Special tags are mutually exclusive and are expected to be located at the start of a line. +# import sys import re +from enum import Enum ## @brief print the whole content of input and match files -def print_content(input_lines, match_lines): +def print_content(input_lines, match_lines, ignored_lines): print("--- Input Lines " + "-" * 64) print("".join(input_lines).strip()) print("--- Match Lines " + "-" * 64) print("".join(match_lines).strip()) + print("--- Ignored Lines " + "-" * 62) + print("".join(ignored_lines).strip()) print("-" * 80) -if len(sys.argv) != 3: - print("Usage: python match.py ") - sys.exit(1) - -input_file = sys.argv[1] -match_file = sys.argv[2] - -with open(input_file, 'r') as input, open(match_file, 'r') as match: - input_lines = input.readlines() - match_lines = match.readlines() - -if len(match_lines) < len(input_lines): - print(f"Match length < input length (input: {len(input_lines)}, match: {len(match_lines)})") - print_content(input_lines, match_lines) - sys.exit(1) - -input_idx = 0 -opt = "{{OPT}}" -for i, match_line in enumerate(match_lines): - if match_line.startswith(opt): - optional_line = True - match_line = match_line[len(opt):] - else: - optional_line = False - - # split into parts at {{ }} - match_parts = re.split(r'\{{(.*?)\}}', match_line.strip()) - pattern = "" - for j, part in enumerate(match_parts): - if j % 2 == 0: - pattern += re.escape(part) - else: - pattern += part +def print_incorrect_match(match_line, present, expected): + print("Line " + str(match_line) + " does not match") + print("is: " + present) + print("expected: " + expected) - # empty input file or end of input file, from now on match file must be optional - if not input_lines: - if optional_line is True: - continue - else: - print("End of input file or empty file.") - print("expected: " + match_line.strip()) - sys.exit(1) - input_line = input_lines[input_idx].strip() - if not re.fullmatch(pattern, input_line): - if optional_line is True: - continue - else: - print("Line " + str(i+1) + " does not match") - print("is: " + input_line) - print("expected: " + match_line.strip()) - print_content(input_lines, match_lines) +## @brief pattern matching script status values +class Status(Enum): + INPUT_END = 1 + MATCH_END = 2 + INPUT_AND_MATCH_END = 3 + PROCESSING = 4 + + +## @brief check matching script status +def check_status(input_lines, match_lines): + if not input_lines and not match_lines: + return Status.INPUT_AND_MATCH_END + elif not input_lines: + return Status.INPUT_END + elif not match_lines: + return Status.MATCH_END + return Status.PROCESSING + + +## @brief pattern matching tags. +## Tags are expected to be at the start of the line. +class Tag(Enum): + OPT = "{{OPT}}" # makes the line optional + IGNORE = "{{IGNORE}}" # ignores all input until next match or end of input file + + +## @brief main function for the match file processing script +def main(): + if len(sys.argv) != 3: + print("Usage: python match.py ") + sys.exit(1) + + input_file = sys.argv[1] + match_file = sys.argv[2] + + with open(input_file, 'r') as input, open(match_file, 'r') as match: + input_lines = input.readlines() + match_lines = match.readlines() + + ignored_lines = [] + + input_idx = 0 + match_idx = 0 + tag_in_effect = None + status = Status.PROCESSING + while status == Status.PROCESSING: + status = check_status(input_lines[input_idx:], match_lines[match_idx:]) + if status == Status.INPUT_AND_MATCH_END: + sys.exit(0) + elif status == Status.MATCH_END: + # last line of the match file is {{IGNORE}} tag, stop processing + if tag_in_effect == Tag.IGNORE: + sys.exit(0) + else: + print_incorrect_match(match_idx + 1, input_lines[input_idx].strip(), ""); + print_content(input_lines, match_lines, ignored_lines) + sys.exit(1) + elif status == Status.INPUT_END: + print_incorrect_match(match_idx + 1, "", match_lines[match_idx].strip()); + print_content(input_lines, match_lines, ignored_lines) sys.exit(1) - else: - if (input_idx == len(input_lines) - 1): - input_lines = [] else: - input_idx += 1 \ No newline at end of file + input_line = input_lines[input_idx].strip() + + match_line = match_lines[match_idx] + if match_line.startswith(Tag.OPT.value): + tag_in_effect = Tag.OPT + match_line = match_line[len(Tag.OPT.value):] + elif match_line.startswith(Tag.IGNORE.value): + tag_in_effect = Tag.IGNORE + match_idx += 1 + continue + + # split into parts at {{ }} + match_parts = re.split(r'\{{(.*?)\}}', match_line.strip()) + pattern = "" + for j, part in enumerate(match_parts): + if j % 2 == 0: + pattern += re.escape(part) + else: + pattern += part + + if not re.fullmatch(pattern, input_line): + if tag_in_effect == Tag.OPT: + match_idx += 1 + tag_in_effect = None + continue + elif tag_in_effect == Tag.IGNORE: + ignored_lines.append(input_line + "\n") + input_idx += 1 + continue + else: + print_incorrect_match(match_idx + 1, input_line, match_line.strip()) + print_content(input_lines, match_lines, ignored_lines) + sys.exit(1) + + input_idx += 1 + match_idx += 1 + tag_in_effect = None + + +if __name__ == "__main__": + main()