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