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