• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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