Skip to content

Commit 17d00c1

Browse files
committed
CheckParser: dedicated class that parses CHECKs
1 parent dbef7cf commit 17d00c1

File tree

6 files changed

+171
-136
lines changed

6 files changed

+171
-136
lines changed

filecheck/filecheck.py

Lines changed: 167 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
__version__ = "0.0.20"
1313

14-
from typing import Optional
14+
from typing import Optional, List, Iterable
1515

1616

1717
class FailedCheck:
@@ -241,6 +241,157 @@ def implicit_check_line(check_not_check, strict_mode, line):
241241
return False
242242

243243

244+
class CheckParserEmptyCheckException(BaseException):
245+
def __init__(self, check: Check):
246+
super().__init__()
247+
self.check = check
248+
249+
250+
class CheckParser:
251+
@staticmethod
252+
def parse_checks_from_file(
253+
check_file_path: str, args, strict_mode: bool, check_prefix: str
254+
) -> List[Check]:
255+
with open(check_file_path, encoding="utf-8") as check_file:
256+
return CheckParser.parse_checks_from_strings(
257+
check_file, args, strict_mode, check_prefix
258+
)
259+
260+
@staticmethod
261+
def parse_checks_from_strings(
262+
input_strings: Iterable[str], args, strict_mode: bool, check_prefix: str
263+
) -> List[Check]:
264+
checks = []
265+
for line_idx, line in enumerate(input_strings):
266+
check = CheckParser.parse_check(
267+
line, line_idx, args, strict_mode, check_prefix
268+
)
269+
if check is None:
270+
continue
271+
if check.check_type == CheckType.CHECK_EMPTY and len(checks) == 0:
272+
raise CheckParserEmptyCheckException(check)
273+
checks.append(check)
274+
return checks
275+
276+
@staticmethod
277+
def parse_check(
278+
line: str, line_idx, args, strict_mode: bool, check_prefix: str
279+
) -> Optional[Check]:
280+
line = line.rstrip()
281+
282+
if not args.strict_whitespace:
283+
line = canonicalize_whitespace(line)
284+
285+
# CHECK and CHECK-NEXT
286+
strict_whitespace_match = "" if strict_mode else " *"
287+
288+
check_regex = (
289+
f"{BEFORE_PREFIX}({check_prefix}):{strict_whitespace_match}(.*)"
290+
)
291+
292+
check_match = re.search(check_regex, line)
293+
check_type = CheckType.CHECK
294+
if not check_match:
295+
check_regex = (
296+
f"{BEFORE_PREFIX}({check_prefix}-NEXT):"
297+
f"{strict_whitespace_match}(.*)"
298+
)
299+
check_match = re.search(check_regex, line)
300+
check_type = CheckType.CHECK_NEXT
301+
302+
if check_match:
303+
check_keyword = check_match.group(2)
304+
check_expression = check_match.group(3)
305+
if not strict_mode:
306+
check_expression = check_expression.strip(" ")
307+
308+
match_type = MatchType.SUBSTRING
309+
310+
if re.search(r"\{\{.*\}\}", check_expression):
311+
regex_line = escape_non_regex_parts(check_expression)
312+
regex_line = re.sub(r"\{\{(.*?)\}\}", r"\1", regex_line)
313+
match_type = MatchType.REGEX
314+
check_expression = regex_line
315+
if strict_mode:
316+
if check_expression[0] != "^":
317+
check_expression = "^" + check_expression
318+
if check_expression[-1] != "$":
319+
check_expression = check_expression + "$"
320+
321+
# Replace line number expressions, e.g. `[[# @LINE + 3 ]]`
322+
line_var_match = re.search(LINE_NUMBER_REGEX, check_expression)
323+
while line_var_match is not None:
324+
offset = int(line_var_match.group(2) or 0)
325+
if line_var_match.group(1) == "-":
326+
offset = -offset
327+
check_expression = re.sub(
328+
LINE_NUMBER_REGEX,
329+
str(line_idx + offset + 1),
330+
check_expression,
331+
1,
332+
)
333+
line_var_match = re.search(LINE_NUMBER_REGEX, check_expression)
334+
335+
check = Check(
336+
check_type=check_type,
337+
match_type=match_type,
338+
check_keyword=check_keyword,
339+
expression=check_expression,
340+
source_line=line,
341+
check_line_idx=line_idx,
342+
start_index=check_match.start(3),
343+
)
344+
return check
345+
346+
check_not_regex = (
347+
f"{BEFORE_PREFIX}({check_prefix}-NOT):"
348+
f"{strict_whitespace_match}(.*)"
349+
)
350+
check_match = re.search(check_not_regex, line)
351+
if check_match:
352+
match_type = MatchType.SUBSTRING
353+
354+
check_keyword = check_match.group(2)
355+
check_expression = check_match.group(3)
356+
if not strict_mode:
357+
check_expression = check_expression.strip(" ")
358+
359+
if re.search(r"\{\{.*\}\}", check_expression):
360+
regex_line = escape_non_regex_parts(check_expression)
361+
regex_line = re.sub(r"\{\{(.*?)\}\}", r"\1", regex_line)
362+
match_type = MatchType.REGEX
363+
check_expression = regex_line
364+
365+
check = Check(
366+
check_type=CheckType.CHECK_NOT,
367+
match_type=match_type,
368+
check_keyword=check_keyword,
369+
expression=check_expression,
370+
source_line=line,
371+
check_line_idx=line_idx,
372+
start_index=check_match.start(3),
373+
)
374+
return check
375+
376+
check_empty_regex = f"{BEFORE_PREFIX}({check_prefix}-EMPTY):"
377+
check_match = re.search(check_empty_regex, line)
378+
if check_match:
379+
check_keyword = check_match.group(2)
380+
381+
check = Check(
382+
check_type=CheckType.CHECK_EMPTY,
383+
match_type=MatchType.SUBSTRING,
384+
check_keyword=check_keyword,
385+
expression=None,
386+
source_line=line,
387+
check_line_idx=line_idx,
388+
start_index=check_match.start(2),
389+
)
390+
return check
391+
392+
return None
393+
394+
244395
def main():
245396
# Force UTF-8 to be sent to stdout.
246397
# https://stackoverflow.com/a/3597849/598057
@@ -335,140 +486,21 @@ def exit_handler(code):
335486
print(error_message, file=sys.stderr)
336487
exit_handler(2)
337488

338-
checks = []
339-
with open(check_file_path, encoding="utf-8") as check_file:
340-
for line_idx, line in enumerate(check_file):
341-
line = line.rstrip()
342-
343-
if not args.strict_whitespace:
344-
line = canonicalize_whitespace(line)
345-
346-
# CHECK and CHECK-NEXT
347-
strict_whitespace_match = "" if strict_mode else " *"
348-
349-
check_regex = (
350-
f"{BEFORE_PREFIX}({check_prefix}):{strict_whitespace_match}(.*)"
351-
)
352-
353-
check_match = re.search(check_regex, line)
354-
check_type = CheckType.CHECK
355-
if not check_match:
356-
check_regex = (
357-
f"{BEFORE_PREFIX}({check_prefix}-NEXT):"
358-
f"{strict_whitespace_match}(.*)"
359-
)
360-
check_match = re.search(check_regex, line)
361-
check_type = CheckType.CHECK_NEXT
362-
363-
if check_match:
364-
check_keyword = check_match.group(2)
365-
check_expression = check_match.group(3)
366-
if not strict_mode:
367-
check_expression = check_expression.strip(" ")
368-
369-
match_type = MatchType.SUBSTRING
370-
371-
if re.search(r"\{\{.*\}\}", check_expression):
372-
regex_line = escape_non_regex_parts(check_expression)
373-
regex_line = re.sub(r"\{\{(.*?)\}\}", r"\1", regex_line)
374-
match_type = MatchType.REGEX
375-
check_expression = regex_line
376-
if strict_mode:
377-
if check_expression[0] != "^":
378-
check_expression = "^" + check_expression
379-
if check_expression[-1] != "$":
380-
check_expression = check_expression + "$"
381-
382-
# Replace line number expressions, e.g. `[[# @LINE + 3 ]]`
383-
line_var_match = re.search(LINE_NUMBER_REGEX, check_expression)
384-
while line_var_match is not None:
385-
offset = int(line_var_match.group(2) or 0)
386-
if line_var_match.group(1) == "-":
387-
offset = -offset
388-
check_expression = re.sub(
389-
LINE_NUMBER_REGEX,
390-
str(line_idx + offset + 1),
391-
check_expression,
392-
1,
393-
)
394-
line_var_match = re.search(
395-
LINE_NUMBER_REGEX, check_expression
396-
)
397-
398-
check = Check(
399-
check_type=check_type,
400-
match_type=match_type,
401-
check_keyword=check_keyword,
402-
expression=check_expression,
403-
source_line=line,
404-
check_line_idx=line_idx,
405-
start_index=check_match.start(3),
406-
)
407-
408-
checks.append(check)
409-
continue
410-
411-
check_not_regex = (
412-
f"{BEFORE_PREFIX}({check_prefix}-NOT):"
413-
f"{strict_whitespace_match}(.*)"
414-
)
415-
check_match = re.search(check_not_regex, line)
416-
if check_match:
417-
match_type = MatchType.SUBSTRING
418-
419-
check_keyword = check_match.group(2)
420-
check_expression = check_match.group(3)
421-
if not strict_mode:
422-
check_expression = check_expression.strip(" ")
423-
424-
if re.search(r"\{\{.*\}\}", check_expression):
425-
regex_line = escape_non_regex_parts(check_expression)
426-
regex_line = re.sub(r"\{\{(.*?)\}\}", r"\1", regex_line)
427-
match_type = MatchType.REGEX
428-
check_expression = regex_line
429-
430-
check = Check(
431-
check_type=CheckType.CHECK_NOT,
432-
match_type=match_type,
433-
check_keyword=check_keyword,
434-
expression=check_expression,
435-
source_line=line,
436-
check_line_idx=line_idx,
437-
start_index=check_match.start(3),
438-
)
439-
440-
checks.append(check)
441-
continue
442-
443-
check_empty_regex = f"{BEFORE_PREFIX}({check_prefix}-EMPTY):"
444-
check_match = re.search(check_empty_regex, line)
445-
if check_match:
446-
check_keyword = check_match.group(2)
447-
448-
check = Check(
449-
check_type=CheckType.CHECK_EMPTY,
450-
match_type=MatchType.SUBSTRING,
451-
check_keyword=check_keyword,
452-
expression=None,
453-
source_line=line,
454-
check_line_idx=line_idx,
455-
start_index=check_match.start(2),
456-
)
457-
458-
if len(checks) == 0:
459-
print(
460-
f"{check_file_path}:"
461-
f"{line_idx + 1}:"
462-
f"{check.start_index + 1}: "
463-
f"error: "
464-
f"found 'CHECK-EMPTY' without previous 'CHECK: line"
465-
)
466-
print(line)
467-
print("^".rjust(check.start_index + 1, " "))
468-
exit_handler(2)
469-
470-
checks.append(check)
471-
continue
489+
try:
490+
checks = CheckParser.parse_checks_from_file(
491+
check_file_path, args, strict_mode, check_prefix
492+
)
493+
except CheckParserEmptyCheckException as exception:
494+
print(
495+
f"{check_file_path}:"
496+
f"{exception.check.check_line_idx + 1}:"
497+
f"{exception.check.start_index + 1}: "
498+
f"error: "
499+
f"found 'CHECK-EMPTY' without previous 'CHECK: line"
500+
)
501+
print(exception.check.source_line)
502+
print("^".rjust(exception.check.start_index + 1, " "))
503+
exit_handler(2)
472504

473505
check_iterator = iter(checks)
474506

Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
; CHECK:{{^.*filecheck.check:1:(9|10): error: CHECK: expected string not found in input$}}
1+
CHECK:{{^.*filecheck.check:1:(9|10): error: CHECK: expected string not found in input$}}

tests/integration/tests/check_commands/CHECK/one_string/positive-match/05-CHECK-two-times-matches-first/sample.itest renamed to tests/integration/tests/check_commands/CHECK/one_string/positive-match/05-CHECK-two-times-matches-first/sample.itest__

File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
; CHECK:{{^.*filecheck.check:1:(9|10): error: CHECK: expected string not found in input$}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A-very-long-path/filecheck.check:1:9: error: CHECK: expected string not found in input
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
RUN: %cat "%S/filecheck.input" | %expect_exit 0 --expect-no-content %FILECHECK_EXEC "%S/filecheck.check"

0 commit comments

Comments
 (0)