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 __processLine(line, lineNo, prefix, fileName):
48  """ This function is invoked on each line of the check file and returns a triplet
49      which instructs the parser how the line should be handled. If the line is
50      to be included in the current check group, it is returned in the first
51      value. If the line starts a new check group, the name of the group is
52      returned in the second value. The third value indicates whether the line
53      contained an architecture-specific suffix.
54  """
55  if not __isCheckerLine(line):
56    return None, None, None
57
58  # Lines beginning with 'CHECK-START' start a new test case.
59  # We currently only consider the architecture suffix in "CHECK-START" lines.
60  for debuggable in [True, False]:
61    for arch in [None] + archs_list:
62      startLine = __extractLine(prefix + "-START", line, arch, debuggable)
63      if startLine is not None:
64        return None, startLine, (arch, debuggable)
65
66  # Lines starting only with 'CHECK' are matched in order.
67  plainLine = __extractLine(prefix, line)
68  if plainLine is not None:
69    return (plainLine, TestAssertion.Variant.InOrder, lineNo), None, None
70
71  # 'CHECK-NEXT' lines are in-order but must match the very next line.
72  nextLine = __extractLine(prefix + "-NEXT", line)
73  if nextLine is not None:
74    return (nextLine, TestAssertion.Variant.NextLine, lineNo), None, None
75
76  # 'CHECK-DAG' lines are no-order assertions.
77  dagLine = __extractLine(prefix + "-DAG", line)
78  if dagLine is not None:
79    return (dagLine, TestAssertion.Variant.DAG, lineNo), None, None
80
81  # 'CHECK-NOT' lines are no-order negative assertions.
82  notLine = __extractLine(prefix + "-NOT", line)
83  if notLine is not None:
84    return (notLine, TestAssertion.Variant.Not, lineNo), None, None
85
86  # 'CHECK-EVAL' lines evaluate a Python expression.
87  evalLine = __extractLine(prefix + "-EVAL", line)
88  if evalLine is not None:
89    return (evalLine, TestAssertion.Variant.Eval, lineNo), None, None
90
91  Logger.fail("Checker assertion could not be parsed: '" + line + "'", fileName, lineNo)
92
93def __isMatchAtStart(match):
94  """ Tests if the given Match occurred at the beginning of the line. """
95  return (match is not None) and (match.start() == 0)
96
97def __firstMatch(matches, string):
98  """ Takes in a list of Match objects and returns the minimal start point among
99      them. If there aren't any successful matches it returns the length of
100      the searched string.
101  """
102  starts = map(lambda m: len(string) if m is None else m.start(), matches)
103  return min(starts)
104
105def ParseCheckerAssertion(parent, line, variant, lineNo):
106  """ This method parses the content of a check line stripped of the initial
107      comment symbol and the CHECK-* keyword.
108  """
109  assertion = TestAssertion(parent, variant, line, lineNo)
110  isEvalLine = (variant == TestAssertion.Variant.Eval)
111
112  # Loop as long as there is something to parse.
113  while line:
114    # Search for the nearest occurrence of the special markers.
115    if isEvalLine:
116      # The following constructs are not supported in CHECK-EVAL lines
117      matchWhitespace = None
118      matchPattern = None
119      matchVariableDefinition = None
120    else:
121      matchWhitespace = re.search(r"\s+", line)
122      matchPattern = re.search(TestExpression.Regex.regexPattern, line)
123      matchVariableDefinition = re.search(TestExpression.Regex.regexVariableDefinition, line)
124    matchVariableReference = re.search(TestExpression.Regex.regexVariableReference, line)
125
126    # If one of the above was identified at the current position, extract them
127    # from the line, parse them and add to the list of line parts.
128    if __isMatchAtStart(matchWhitespace):
129      # A whitespace in the check line creates a new separator of line parts.
130      # This allows for ignored output between the previous and next parts.
131      line = line[matchWhitespace.end():]
132      assertion.addExpression(TestExpression.createSeparator())
133    elif __isMatchAtStart(matchPattern):
134      pattern = line[0:matchPattern.end()]
135      pattern = pattern[2:-2]
136      line = line[matchPattern.end():]
137      assertion.addExpression(TestExpression.createPattern(pattern))
138    elif __isMatchAtStart(matchVariableReference):
139      var = line[0:matchVariableReference.end()]
140      line = line[matchVariableReference.end():]
141      name = var[2:-2]
142      assertion.addExpression(TestExpression.createVariableReference(name))
143    elif __isMatchAtStart(matchVariableDefinition):
144      var = line[0:matchVariableDefinition.end()]
145      line = line[matchVariableDefinition.end():]
146      colonPos = var.find(":")
147      name = var[2:colonPos]
148      body = var[colonPos+1:-2]
149      assertion.addExpression(TestExpression.createVariableDefinition(name, body))
150    else:
151      # If we're not currently looking at a special marker, this is a plain
152      # text match all the way until the first special marker (or the end
153      # of the line).
154      firstMatch = __firstMatch([ matchWhitespace,
155                                  matchPattern,
156                                  matchVariableReference,
157                                  matchVariableDefinition ],
158                                line)
159      text = line[0:firstMatch]
160      line = line[firstMatch:]
161      if isEvalLine:
162        assertion.addExpression(TestExpression.createPlainText(text))
163      else:
164        assertion.addExpression(TestExpression.createPatternFromPlainText(text))
165  return assertion
166
167def ParseCheckerStream(fileName, prefix, stream):
168  checkerFile = CheckerFile(fileName)
169  fnProcessLine = lambda line, lineNo: __processLine(line, lineNo, prefix, fileName)
170  fnLineOutsideChunk = lambda line, lineNo: \
171      Logger.fail("Checker line not inside a group", fileName, lineNo)
172  for caseName, caseLines, startLineNo, testData in \
173      SplitStream(stream, fnProcessLine, fnLineOutsideChunk):
174    testArch = testData[0]
175    forDebuggable = testData[1]
176    testCase = TestCase(checkerFile, caseName, startLineNo, testArch, forDebuggable)
177    for caseLine in caseLines:
178      ParseCheckerAssertion(testCase, caseLine[0], caseLine[1], caseLine[2])
179  return checkerFile
180