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, TestStatement, 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, TestStatement.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, TestStatement.Variant.NextLine, lineNo), None, None 102 103 # 'CHECK-DAG' lines are no-order statements. 104 dagLine = __extractLine(prefix + "-DAG", line) 105 if dagLine is not None: 106 return (dagLine, TestStatement.Variant.DAG, lineNo), None, None 107 108 # 'CHECK-NOT' lines are no-order negative statements. 109 notLine = __extractLine(prefix + "-NOT", line) 110 if notLine is not None: 111 return (notLine, TestStatement.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, TestStatement.Variant.Eval, lineNo), None, None 117 118 # 'CHECK-IF' lines mark the beginning of a block that will be executed 119 # only if the Python expression that follows evaluates to true. 120 ifLine = __extractLine(prefix + "-IF", line) 121 if ifLine is not None: 122 return (ifLine, TestStatement.Variant.If, lineNo), None, None 123 124 # 'CHECK-ELIF' lines mark the beginning of an `else if` branch of a CHECK-IF block. 125 elifLine = __extractLine(prefix + "-ELIF", line) 126 if elifLine is not None: 127 return (elifLine, TestStatement.Variant.Elif, lineNo), None, None 128 129 # 'CHECK-ELSE' lines mark the beginning of the `else` branch of a CHECK-IF block. 130 elseLine = __extractLine(prefix + "-ELSE", line) 131 if elseLine is not None: 132 return (elseLine, TestStatement.Variant.Else, lineNo), None, None 133 134 # 'CHECK-FI' lines mark the end of a CHECK-IF block. 135 fiLine = __extractLine(prefix + "-FI", line) 136 if fiLine is not None: 137 return (fiLine, TestStatement.Variant.Fi, lineNo), None, None 138 139 Logger.fail("Checker statement could not be parsed: '" + line + "'", fileName, lineNo) 140 141def __isMatchAtStart(match): 142 """ Tests if the given Match occurred at the beginning of the line. """ 143 return (match is not None) and (match.start() == 0) 144 145def __firstMatch(matches, string): 146 """ Takes in a list of Match objects and returns the minimal start point among 147 them. If there aren't any successful matches it returns the length of 148 the searched string. 149 """ 150 starts = map(lambda m: len(string) if m is None else m.start(), matches) 151 return min(starts) 152 153def ParseCheckerStatement(parent, line, variant, lineNo): 154 """ This method parses the content of a check line stripped of the initial 155 comment symbol and the CHECK-* keyword. 156 """ 157 statement = TestStatement(parent, variant, line, lineNo) 158 159 if statement.isNoContentStatement() and line: 160 Logger.fail("Expected empty statement: '" + line + "'", statement.fileName, statement.lineNo) 161 162 # Loop as long as there is something to parse. 163 while line: 164 # Search for the nearest occurrence of the special markers. 165 if statement.isEvalContentStatement(): 166 # The following constructs are not supported in CHECK-EVAL, -IF and -ELIF lines 167 matchWhitespace = None 168 matchPattern = None 169 matchVariableDefinition = None 170 else: 171 matchWhitespace = re.search(r"\s+", line) 172 matchPattern = re.search(TestExpression.Regex.regexPattern, line) 173 matchVariableDefinition = re.search(TestExpression.Regex.regexVariableDefinition, line) 174 matchVariableReference = re.search(TestExpression.Regex.regexVariableReference, line) 175 176 # If one of the above was identified at the current position, extract them 177 # from the line, parse them and add to the list of line parts. 178 if __isMatchAtStart(matchWhitespace): 179 # A whitespace in the check line creates a new separator of line parts. 180 # This allows for ignored output between the previous and next parts. 181 line = line[matchWhitespace.end():] 182 statement.addExpression(TestExpression.createSeparator()) 183 elif __isMatchAtStart(matchPattern): 184 pattern = line[0:matchPattern.end()] 185 pattern = pattern[2:-2] 186 line = line[matchPattern.end():] 187 statement.addExpression(TestExpression.createPattern(pattern)) 188 elif __isMatchAtStart(matchVariableReference): 189 var = line[0:matchVariableReference.end()] 190 line = line[matchVariableReference.end():] 191 name = var[2:-2] 192 statement.addExpression(TestExpression.createVariableReference(name)) 193 elif __isMatchAtStart(matchVariableDefinition): 194 var = line[0:matchVariableDefinition.end()] 195 line = line[matchVariableDefinition.end():] 196 colonPos = var.find(":") 197 name = var[2:colonPos] 198 body = var[colonPos+1:-2] 199 statement.addExpression(TestExpression.createVariableDefinition(name, body)) 200 else: 201 # If we're not currently looking at a special marker, this is a plain 202 # text match all the way until the first special marker (or the end 203 # of the line). 204 firstMatch = __firstMatch([ matchWhitespace, 205 matchPattern, 206 matchVariableReference, 207 matchVariableDefinition ], 208 line) 209 text = line[0:firstMatch] 210 line = line[firstMatch:] 211 if statement.isEvalContentStatement(): 212 statement.addExpression(TestExpression.createPlainText(text)) 213 else: 214 statement.addExpression(TestExpression.createPatternFromPlainText(text)) 215 return statement 216 217def ParseCheckerStream(fileName, prefix, stream, targetArch = None): 218 checkerFile = CheckerFile(fileName) 219 fnProcessLine = lambda line, lineNo: __processLine(line, lineNo, prefix, fileName, targetArch) 220 fnLineOutsideChunk = lambda line, lineNo: \ 221 Logger.fail("Checker line not inside a group", fileName, lineNo) 222 for caseName, caseLines, startLineNo, testData in \ 223 SplitStream(stream, fnProcessLine, fnLineOutsideChunk): 224 testArch = testData[0] 225 forDebuggable = testData[1] 226 testCase = TestCase(checkerFile, caseName, startLineNo, testArch, forDebuggable) 227 for caseLine in caseLines: 228 ParseCheckerStatement(testCase, caseLine[0], caseLine[1], caseLine[2]) 229 return checkerFile 230