1- #!/usr/bin/env python
1+ #!/usr/bin/env python3
22
33# Copyright (C) 2023 Intel Corporation
44# Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions.
1212# List of available special tags:
1313# {{OPT}} - makes content in the same line as the tag optional
1414# {{IGNORE}} - ignores all content until the next successfully matched line or the end of the input
15+ # {{NONDETERMINISTIC}} - order of match rules isn't important - each (non OPT) input line is paired with a match line
16+ # in any order
1517# Special tags are mutually exclusive and are expected to be located at the start of a line.
1618#
1719
2022import re
2123from enum import Enum
2224
25+ ## @brief print a sequence of lines
26+ def print_lines (lines , hint = None ):
27+ counter = 1
28+ for l in lines :
29+ hint_char = " "
30+ if hint == counter - 1 :
31+ hint_char = ">"
32+ print ("{}{:4d}| {}" .format (hint_char , counter , l .strip ()))
33+ counter += 1
34+
2335
2436## @brief print the whole content of input and match files
25- def print_content (input_lines , match_lines , ignored_lines ):
26- print ("--- Input Lines " + "-" * 64 )
27- print ( "" . join ( input_lines ). strip () )
28- print ("--- Match Lines " + "-" * 64 )
29- print ( "" . join ( match_lines ). strip () )
30- print ("--- Ignored Lines " + "-" * 62 )
31- print ( "" . join ( ignored_lines ). strip () )
37+ def print_content (input_lines , match_lines , ignored_lines , hint_input = None , hint_match = None ):
38+ print ("------ Input Lines " + "-" * 61 )
39+ print_lines ( input_lines , hint_input )
40+ print ("------ Match Lines " + "-" * 61 )
41+ print_lines ( match_lines , hint_match )
42+ print ("------ Ignored Lines " + "-" * 59 )
43+ print_lines ( ignored_lines )
3244 print ("-" * 80 )
3345
3446
@@ -39,6 +51,24 @@ def print_incorrect_match(match_line, present, expected):
3951 print ("expected: " + expected )
4052
4153
54+ ## @brief print missing match line
55+ def print_input_not_found (input_line , input ):
56+ print ("Input line " + str (input_line ) + " has no match line" )
57+ print ("is: " + input )
58+
59+
60+ ## @brief print missing input line
61+ def print_match_not_found (match_line , input ):
62+ print ("Match line " + str (match_line ) + " has no input line" )
63+ print ("is: " + input )
64+
65+
66+ ## @brief print general syntax error
67+ def print_error (text , match_line ):
68+ print ("Line " + str (match_line ) + " encountered an error" )
69+ print (text )
70+
71+
4272## @brief pattern matching script status values
4373class Status (Enum ):
4474 INPUT_END = 1
@@ -63,6 +93,7 @@ def check_status(input_lines, match_lines):
6393class Tag (Enum ):
6494 OPT = "{{OPT}}" # makes the line optional
6595 IGNORE = "{{IGNORE}}" # ignores all input until next match or end of input file
96+ NONDETERMINISTIC = "{{NONDETERMINISTIC}}" # switches on "deterministic mode"
6697 COMMENT = "#" # comment - line ignored
6798
6899
@@ -88,32 +119,53 @@ def main():
88119 )
89120
90121 ignored_lines = []
122+ matched_lines = set ()
91123
92124 input_idx = 0
93125 match_idx = 0
94126 tags_in_effect = []
127+ deterministic_mode = False
95128 while True :
96129 # check file status
97130 status = check_status (input_lines [input_idx :], match_lines [match_idx :])
98- if (status == Status .INPUT_AND_MATCH_END ) or (status == Status .MATCH_END and Tag .IGNORE in tags_in_effect ):
99- # all lines matched or the last line in match file is an ignore tag
100- sys .exit (0 )
101- elif status == Status .MATCH_END :
102- print_incorrect_match (match_idx + 1 , input_lines [input_idx ].strip (), "" );
103- print_content (input_lines , match_lines , ignored_lines )
104- sys .exit (1 )
105- elif status == Status .INPUT_END :
106- # If we get to the end of the input, but still have pending matches,
107- # then that's a failure unless all pending matches are optional -
108- # otherwise we're done
109- while match_idx < len (match_lines ):
110- if not (match_lines [match_idx ].startswith (Tag .OPT .value ) or
111- match_lines [match_idx ].startswith (Tag .IGNORE .value )):
112- print_incorrect_match (match_idx + 1 , "" , match_lines [match_idx ]);
113- print_content (input_lines , match_lines , ignored_lines )
131+ if deterministic_mode :
132+ if status == Status .INPUT_END :
133+ # Convert the list of seen matches to the list of unseen matches
134+ remaining_matches = set (range (len (match_lines ))) - matched_lines
135+ for m in remaining_matches :
136+ line = match_lines [m ]
137+ if line .startswith (Tag .OPT .value ) or line .startswith (Tag .NONDETERMINISTIC .value ):
138+ continue
139+ print_match_not_found (m + 1 , match_lines [m ])
140+ print_content (input_lines , match_lines , ignored_lines , hint_match = m )
114141 sys .exit (1 )
115- match_idx += 1
116- sys .exit (0 )
142+
143+ sys .exit (0 )
144+ elif status == Status .MATCH_END :
145+ print_input_not_found (input_idx + 1 , input_lines [input_idx ])
146+ print_content (input_lines , match_lines , ignored_lines , hint_input = input_idx )
147+ sys .exit (1 )
148+ else :
149+ if (status == Status .INPUT_AND_MATCH_END ) or (status == Status .MATCH_END and Tag .IGNORE in tags_in_effect ):
150+ # all lines matched or the last line in match file is an ignore tag
151+ sys .exit (0 )
152+ elif status == Status .MATCH_END :
153+ print_incorrect_match (input_idx + 1 , input_lines [input_idx ].strip (), "" )
154+ print_content (input_lines , match_lines , ignored_lines , hint_input = input_idx )
155+ sys .exit (1 )
156+ elif status == Status .INPUT_END :
157+ # If we get to the end of the input, but still have pending matches,
158+ # then that's a failure unless all pending matches are optional -
159+ # otherwise we're done
160+ while match_idx < len (match_lines ):
161+ if not (match_lines [match_idx ].startswith (Tag .OPT .value ) or
162+ match_lines [match_idx ].startswith (Tag .IGNORE .value ) or
163+ match_lines [match_idx ].startswith (Tag .NONDETERMINISTIC .value )):
164+ print_incorrect_match (match_idx + 1 , "" , match_lines [match_idx ])
165+ print_content (input_lines , match_lines , ignored_lines , hint_match = match_idx )
166+ sys .exit (1 )
167+ match_idx += 1
168+ sys .exit (0 )
117169
118170 input_line = input_lines [input_idx ].strip () if input_idx < len (input_lines ) else ""
119171 match_line = match_lines [match_idx ]
@@ -122,7 +174,15 @@ def main():
122174 if match_line .startswith (Tag .OPT .value ):
123175 tags_in_effect .append (Tag .OPT )
124176 match_line = match_line [len (Tag .OPT .value ):]
177+ elif match_line .startswith (Tag .NONDETERMINISTIC .value ) and not deterministic_mode :
178+ deterministic_mode = True
179+ match_idx = 0
180+ input_idx = 0
181+ continue
125182 elif match_line .startswith (Tag .IGNORE .value ):
183+ if deterministic_mode :
184+ print_error (r"Can't use \{{IGNORE\}} in deterministic mode" )
185+ sys .exit (2 )
126186 tags_in_effect .append (Tag .IGNORE )
127187 match_idx += 1
128188 continue # line with ignore tag should be skipped
@@ -137,20 +197,29 @@ def main():
137197 pattern += part
138198
139199 # match or process tags
140- if re .fullmatch (pattern , input_line ):
141- input_idx += 1
142- match_idx += 1
143- tags_in_effect = []
144- elif Tag .OPT in tags_in_effect :
145- match_idx += 1
146- tags_in_effect .remove (Tag .OPT )
147- elif Tag .IGNORE in tags_in_effect :
148- ignored_lines .append (input_line + os .linesep )
149- input_idx += 1
200+ if deterministic_mode :
201+ if re .fullmatch (pattern , input_line ) and match_idx not in matched_lines :
202+ input_idx += 1
203+ matched_lines .add (match_idx )
204+ match_idx = 0
205+ tags_in_effect = []
206+ else :
207+ match_idx += 1
150208 else :
151- print_incorrect_match (match_idx + 1 , input_line , match_line .strip ())
152- print_content (input_lines , match_lines , ignored_lines )
153- sys .exit (1 )
209+ if re .fullmatch (pattern , input_line ):
210+ input_idx += 1
211+ match_idx += 1
212+ tags_in_effect = []
213+ elif Tag .OPT in tags_in_effect :
214+ match_idx += 1
215+ tags_in_effect .remove (Tag .OPT )
216+ elif Tag .IGNORE in tags_in_effect :
217+ ignored_lines .append (input_line + os .linesep )
218+ input_idx += 1
219+ else :
220+ print_incorrect_match (match_idx + 1 , input_line , match_line .strip ())
221+ print_content (input_lines , match_lines , ignored_lines , hint_match = match_idx , hint_input = input_idx )
222+ sys .exit (1 )
154223
155224
156225if __name__ == "__main__" :
0 commit comments