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