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 SplitStream
18from file_format.checker.struct import CheckerFile, TestCase, TestAssertion, TestExpression
19
20import re
21
22def __isCheckerLine(line):
23  return line.startswith("///") or line.startswith("##")
24
25def __extractLine(prefix, line, arch = None, debuggable = False):
26  """ Attempts to parse a check line. The regex searches for a comment symbol
27      followed by the CHECK keyword, given attribute and a colon at the very
28      beginning of the line. Whitespaces are ignored.
29  """
30  rIgnoreWhitespace = r"\s*"
31  rCommentSymbols = [r"///", r"##"]
32  arch_specifier = r"-%s" % arch if arch is not None else r""
33  dbg_specifier = r"-DEBUGGABLE" if debuggable else r""
34  regexPrefix = rIgnoreWhitespace + \
35                r"(" + r"|".join(rCommentSymbols) + r")" + \
36                rIgnoreWhitespace + \
37                prefix + arch_specifier + dbg_specifier + r":"
38
39  # The 'match' function succeeds only if the pattern is matched at the
40  # beginning of the line.
41  match = re.match(regexPrefix, line)
42  if match is not None:
43    return line[match.end():].strip()
44  else:
45    return None
46
47def __preprocessLineForStart(prefix, line, targetArch):
48  """ This function modifies a CHECK-START-{x,y,z} into a matching
49      CHECK-START-y line for matching targetArch y. If no matching
50      architecture is found, CHECK-START-x is returned arbitrarily
51      to ensure all following check lines are put into a test that
52      is skipped. Any other line is left unmodified.
53  """
54  if targetArch is not None:
55    if prefix in line:
56      # Find { } on the line and assume that defines the set.
57      s = line.find('{')
58      e = line.find('}')
59      if 0 < s and s < e:
60        archs = line[s+1:e].split(',')
61        # First verify that every archs is valid. Return the
62        # full line on failure to prompt error back to user.
63        for arch in archs:
64          if not arch in archs_list:
65            return line
66        # Now accept matching arch or arbitrarily return first.
67        if targetArch in archs:
68          return line[:s] + targetArch + line[e + 1:]
69        else:
70          return line[:s] + archs[0] + line[e + 1:]
71  return line
72
73def __processLine(line, lineNo, prefix, fileName, targetArch):
74  """ This function is invoked on each line of the check file and returns a triplet
75      which instructs the parser how the line should be handled. If the line is
76      to be included in the current check group, it is returned in the first
77      value. If the line starts a new check group, the name of the group is
78      returned in the second value. The third value indicates whether the line
79      contained an architecture-specific suffix.
80  """
81  if not __isCheckerLine(line):
82    return None, None, None
83
84  # Lines beginning with 'CHECK-START' start a new test case.
85  # We currently only consider the architecture suffix(es) in "CHECK-START" lines.
86  for debuggable in [True, False]:
87    sline = __preprocessLineForStart(prefix + "-START", line, targetArch)
88    for arch in [None] + archs_list:
89      startLine = __extractLine(prefix + "-START", sline, arch, debuggable)
90      if startLine is not None:
91        return None, startLine, (arch, debuggable)
92
93  # Lines starting only with 'CHECK' are matched in order.
94  plainLine = __extractLine(prefix, line)
95  if plainLine is not None:
96    return (plainLine, TestAssertion.Variant.InOrder, lineNo), None, None
97
98  # 'CHECK-NEXT' lines are in-order but must match the very next line.
99  nextLine = __extractLine(prefix + "-NEXT", line)
100  if nextLine is not None:
101    return (nextLine, TestAssertion.Variant.NextLine, lineNo), None, None
102
103  # 'CHECK-DAG' lines are no-order assertions.
104  dagLine = __extractLine(prefix + "-DAG", line)
105  if dagLine is not None:
106    return (dagLine, TestAssertion.Variant.DAG, lineNo), None, None
107
108  # 'CHECK-NOT' lines are no-order negative assertions.
109  notLine = __extractLine(prefix + "-NOT", line)
110  if notLine is not None:
111    return (notLine, TestAssertion.Variant.Not, lineNo), None, None
112
113  # 'CHECK-EVAL' lines evaluate a Python expression.
114  evalLine = __extractLine(prefix + "-EVAL", line)
115  if evalLine is not None:
116    return (evalLine, TestAssertion.Variant.Eval, lineNo), None, None
117
118  Logger.fail("Checker assertion could not be parsed: '" + line + "'", fileName, lineNo)
119
120def __isMatchAtStart(match):
121  """ Tests if the given Match occurred at the beginning of the line. """
122  return (match is not None) and (match.start() == 0)
123
124def __firstMatch(matches, string):
125  """ Takes in a list of Match objects and returns the minimal start point among
126      them. If there aren't any successful matches it returns the length of
127      the searched string.
128  """
129  starts = map(lambda m: len(string) if m is None else m.start(), matches)
130  return min(starts)
131
132def ParseCheckerAssertion(parent, line, variant, lineNo):
133  """ This method parses the content of a check line stripped of the initial
134      comment symbol and the CHECK-* keyword.
135  """
136  assertion = TestAssertion(parent, variant, line, lineNo)
137  isEvalLine = (variant == TestAssertion.Variant.Eval)
138
139  # Loop as long as there is something to parse.
140  while line:
141    # Search for the nearest occurrence of the special markers.
142    if isEvalLine:
143      # The following constructs are not supported in CHECK-EVAL lines
144      matchWhitespace = None
145      matchPattern = None
146      matchVariableDefinition = None
147    else:
148      matchWhitespace = re.search(r"\s+", line)
149      matchPattern = re.search(TestExpression.Regex.regexPattern, line)
150      matchVariableDefinition = re.search(TestExpression.Regex.regexVariableDefinition, line)
151    matchVariableReference = re.search(TestExpression.Regex.regexVariableReference, line)
152
153    # If one of the above was identified at the current position, extract them
154    # from the line, parse them and add to the list of line parts.
155    if __isMatchAtStart(matchWhitespace):
156      # A whitespace in the check line creates a new separator of line parts.
157      # This allows for ignored output between the previous and next parts.
158      line = line[matchWhitespace.end():]
159      assertion.addExpression(TestExpression.createSeparator())
160    elif __isMatchAtStart(matchPattern):
161      pattern = line[0:matchPattern.end()]
162      pattern = pattern[2:-2]
163      line = line[matchPattern.end():]
164      assertion.addExpression(TestExpression.createPattern(pattern))
165    elif __isMatchAtStart(matchVariableReference):
166      var = line[0:matchVariableReference.end()]
167      line = line[matchVariableReference.end():]
168      name = var[2:-2]
169      assertion.addExpression(TestExpression.createVariableReference(name))
170    elif __isMatchAtStart(matchVariableDefinition):
171      var = line[0:matchVariableDefinition.end()]
172      line = line[matchVariableDefinition.end():]
173      colonPos = var.find(":")
174      name = var[2:colonPos]
175      body = var[colonPos+1:-2]
176      assertion.addExpression(TestExpression.createVariableDefinition(name, body))
177    else:
178      # If we're not currently looking at a special marker, this is a plain
179      # text match all the way until the first special marker (or the end
180      # of the line).
181      firstMatch = __firstMatch([ matchWhitespace,
182                                  matchPattern,
183                                  matchVariableReference,
184                                  matchVariableDefinition ],
185                                line)
186      text = line[0:firstMatch]
187      line = line[firstMatch:]
188      if isEvalLine:
189        assertion.addExpression(TestExpression.createPlainText(text))
190      else:
191        assertion.addExpression(TestExpression.createPatternFromPlainText(text))
192  return assertion
193
194def ParseCheckerStream(fileName, prefix, stream, targetArch = None):
195  checkerFile = CheckerFile(fileName)
196  fnProcessLine = lambda line, lineNo: __processLine(line, lineNo, prefix, fileName, targetArch)
197  fnLineOutsideChunk = lambda line, lineNo: \
198      Logger.fail("Checker line not inside a group", fileName, lineNo)
199  for caseName, caseLines, startLineNo, testData in \
200      SplitStream(stream, fnProcessLine, fnLineOutsideChunk):
201    testArch = testData[0]
202    forDebuggable = testData[1]
203    testCase = TestCase(checkerFile, caseName, startLineNo, testArch, forDebuggable)
204    for caseLine in caseLines:
205      ParseCheckerAssertion(testCase, caseLine[0], caseLine[1], caseLine[2])
206  return checkerFile
207