1# Copyright (C) 2014 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15from common.archs import archs_list 16from common.logger import Logger 17from file_format.common import split_stream 18from file_format.checker.struct import CheckerFile, TestCase, TestStatement, TestExpression 19 20import re 21 22 23def _is_checker_line(line): 24 return line.startswith("///") or line.startswith("##") or line.startswith(";;") 25 26 27def _extract_line(prefix, line, arch=None, debuggable=False): 28 """ Attempts to parse a check line. The regex searches for a comment symbol 29 followed by the CHECK keyword, given attribute and a colon at the very 30 beginning of the line. Whitespaces are ignored. 31 """ 32 r_ignore_whitespace = r"\s*" 33 r_comment_symbols = ["///", "##", ";;"] 34 arch_specifier = "-{}".format(arch) if arch is not None else "" 35 dbg_specifier = "-DEBUGGABLE" if debuggable else "" 36 regex_prefix = (r_ignore_whitespace + 37 "(" + "|".join(r_comment_symbols) + ")" 38 + r_ignore_whitespace + prefix + arch_specifier + dbg_specifier + ":") 39 40 # The 'match' function succeeds only if the pattern is matched at the 41 # beginning of the line. 42 match = re.match(regex_prefix, line) 43 if match is not None: 44 return line[match.end():].strip() 45 else: 46 return None 47 48 49def _preprocess_line_for_start(prefix, line, target_arch): 50 """ This function modifies a CHECK-START-{x,y,z} into a matching 51 CHECK-START-y line for matching targetArch y. If no matching 52 architecture is found, CHECK-START-x is returned arbitrarily 53 to ensure all following check lines are put into a test that 54 is skipped. Any other line is left unmodified. 55 """ 56 if target_arch is not None: 57 if prefix in line: 58 # Find { } on the line and assume that defines the set. 59 s = line.find("{") 60 e = line.find("}") 61 if 0 < s < e: 62 archs = line[s + 1:e].split(",") 63 # First verify that every archs is valid. Return the 64 # full line on failure to prompt error back to user. 65 for arch in archs: 66 if arch not in archs_list: 67 return line 68 # Now accept matching arch or arbitrarily return first. 69 if target_arch in archs: 70 return line[:s] + target_arch + line[e + 1:] 71 else: 72 return line[:s] + archs[0] + line[e + 1:] 73 return line 74 75 76def _process_line(line, line_no, prefix, filename, target_arch): 77 """ This function is invoked on each line of the check file and returns a triplet 78 which instructs the parser how the line should be handled. If the line is 79 to be included in the current check group, it is returned in the first 80 value. If the line starts a new check group, the name of the group is 81 returned in the second value. The third value indicates whether the line 82 contained an architecture-specific suffix. 83 """ 84 if not _is_checker_line(line): 85 return None, None, None 86 87 # Lines beginning with 'CHECK-START' start a new test case. 88 # We currently only consider the architecture suffix(es) in "CHECK-START" lines. 89 for debuggable in [True, False]: 90 sline = _preprocess_line_for_start(prefix + "-START", line, target_arch) 91 for arch in [None] + archs_list: 92 start_line = _extract_line(prefix + "-START", sline, arch, debuggable) 93 if start_line is not None: 94 return None, start_line, (arch, debuggable) 95 96 # Lines starting only with 'CHECK' are matched in order. 97 plain_line = _extract_line(prefix, line) 98 if plain_line is not None: 99 return (plain_line, TestStatement.Variant.IN_ORDER, line_no), None, None 100 101 # 'CHECK-NEXT' lines are in-order but must match the very next line. 102 next_line = _extract_line(prefix + "-NEXT", line) 103 if next_line is not None: 104 return (next_line, TestStatement.Variant.NEXT_LINE, line_no), None, None 105 106 # 'CHECK-DAG' lines are no-order statements. 107 dag_line = _extract_line(prefix + "-DAG", line) 108 if dag_line is not None: 109 return (dag_line, TestStatement.Variant.DAG, line_no), None, None 110 111 # 'CHECK-NOT' lines are no-order negative statements. 112 not_line = _extract_line(prefix + "-NOT", line) 113 if not_line is not None: 114 return (not_line, TestStatement.Variant.NOT, line_no), None, None 115 116 # 'CHECK-EVAL' lines evaluate a Python expression. 117 eval_line = _extract_line(prefix + "-EVAL", line) 118 if eval_line is not None: 119 return (eval_line, TestStatement.Variant.EVAL, line_no), None, None 120 121 # 'CHECK-IF' lines mark the beginning of a block that will be executed 122 # only if the Python expression that follows evaluates to true. 123 if_line = _extract_line(prefix + "-IF", line) 124 if if_line is not None: 125 return (if_line, TestStatement.Variant.IF, line_no), None, None 126 127 # 'CHECK-ELIF' lines mark the beginning of an `else if` branch of a CHECK-IF block. 128 elif_line = _extract_line(prefix + "-ELIF", line) 129 if elif_line is not None: 130 return (elif_line, TestStatement.Variant.ELIF, line_no), None, None 131 132 # 'CHECK-ELSE' lines mark the beginning of the `else` branch of a CHECK-IF block. 133 else_line = _extract_line(prefix + "-ELSE", line) 134 if else_line is not None: 135 return (else_line, TestStatement.Variant.ELSE, line_no), None, None 136 137 # 'CHECK-FI' lines mark the end of a CHECK-IF block. 138 fi_line = _extract_line(prefix + "-FI", line) 139 if fi_line is not None: 140 return (fi_line, TestStatement.Variant.FI, line_no), None, None 141 142 Logger.fail("Checker statement could not be parsed: '" + line + "'", filename, line_no) 143 144 145def _is_match_at_start(match): 146 """ Tests if the given Match occurred at the beginning of the line. """ 147 return (match is not None) and (match.start() == 0) 148 149 150def _first_match(matches, string): 151 """ Takes in a list of Match objects and returns the minimal start point among 152 them. If there aren't any successful matches it returns the length of 153 the searched string. 154 """ 155 return min(len(string) if m is None else m.start() for m in matches) 156 157 158def parse_checker_statement(parent, line, variant, line_no): 159 """ This method parses the content of a check line stripped of the initial 160 comment symbol and the CHECK-* keyword. 161 """ 162 statement = TestStatement(parent, variant, line, line_no) 163 164 if statement.is_no_content_statement() and line: 165 Logger.fail("Expected empty statement: '{}'".format(line), statement.filename, 166 statement.line_no) 167 168 # Loop as long as there is something to parse. 169 while line: 170 # Search for the nearest occurrence of the special markers. 171 if statement.is_eval_content_statement(): 172 # The following constructs are not supported in CHECK-EVAL, -IF and -ELIF lines 173 match_whitespace = None 174 match_pattern = None 175 match_variable_definition = None 176 else: 177 match_whitespace = re.search(r"\s+", line) 178 match_pattern = re.search(TestExpression.Regex.REGEX_PATTERN, line) 179 match_variable_definition = re.search(TestExpression.Regex.REGEX_VARIABLE_DEFINITION, line) 180 match_variable_reference = re.search(TestExpression.Regex.REGEX_VARIABLE_REFERENCE, line) 181 182 # If one of the above was identified at the current position, extract them 183 # from the line, parse them and add to the list of line parts. 184 if _is_match_at_start(match_whitespace): 185 # A whitespace in the check line creates a new separator of line parts. 186 # This allows for ignored output between the previous and next parts. 187 line = line[match_whitespace.end():] 188 statement.add_expression(TestExpression.create_separator()) 189 elif _is_match_at_start(match_pattern): 190 pattern = line[0:match_pattern.end()] 191 pattern = pattern[2:-2] 192 line = line[match_pattern.end():] 193 statement.add_expression(TestExpression.create_pattern(pattern)) 194 elif _is_match_at_start(match_variable_reference): 195 var = line[0:match_variable_reference.end()] 196 line = line[match_variable_reference.end():] 197 name = var[2:-2] 198 statement.add_expression(TestExpression.create_variable_reference(name)) 199 elif _is_match_at_start(match_variable_definition): 200 var = line[0:match_variable_definition.end()] 201 line = line[match_variable_definition.end():] 202 colon_pos = var.find(":") 203 name = var[2:colon_pos] 204 body = var[colon_pos + 1:-2] 205 statement.add_expression(TestExpression.create_variable_definition(name, body)) 206 else: 207 # If we're not currently looking at a special marker, this is a plain 208 # text match all the way until the first special marker (or the end 209 # of the line). 210 first_match = _first_match([match_whitespace, 211 match_pattern, 212 match_variable_reference, 213 match_variable_definition], 214 line) 215 text = line[0:first_match] 216 line = line[first_match:] 217 if statement.is_eval_content_statement(): 218 statement.add_expression(TestExpression.create_plain_text(text)) 219 else: 220 statement.add_expression(TestExpression.create_pattern_from_plain_text(text)) 221 return statement 222 223 224def parse_checker_stream(file_name, prefix, stream, target_arch=None): 225 checker_file = CheckerFile(file_name) 226 227 def fn_process_line(line, line_no): 228 return _process_line(line, line_no, prefix, file_name, target_arch) 229 230 def fn_line_outside_chunk(line, line_no): 231 Logger.fail("Checker line not inside a group", file_name, line_no) 232 233 for case_name, case_lines, start_line_no, test_data in split_stream(stream, fn_process_line, 234 fn_line_outside_chunk): 235 test_arch = test_data[0] 236 for_debuggable = test_data[1] 237 test_case = TestCase(checker_file, case_name, start_line_no, test_arch, for_debuggable) 238 for case_line in case_lines: 239 parse_checker_statement(test_case, case_line[0], case_line[1], case_line[2]) 240 return checker_file 241