diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | a27d2baa0c1a2ec70f47ea9199b1dd6762c8a349 (patch) | |
tree | defd1cc07d16ad2f3b21154114e092d11c94c5bb /libc/kernel/tools/cpp.py | |
download | bionic-a27d2baa0c1a2ec70f47ea9199b1dd6762c8a349.zip bionic-a27d2baa0c1a2ec70f47ea9199b1dd6762c8a349.tar.gz bionic-a27d2baa0c1a2ec70f47ea9199b1dd6762c8a349.tar.bz2 |
Initial Contributionandroid-1.0
Diffstat (limited to 'libc/kernel/tools/cpp.py')
-rw-r--r-- | libc/kernel/tools/cpp.py | 2159 |
1 files changed, 2159 insertions, 0 deletions
diff --git a/libc/kernel/tools/cpp.py b/libc/kernel/tools/cpp.py new file mode 100644 index 0000000..33f1a06 --- /dev/null +++ b/libc/kernel/tools/cpp.py @@ -0,0 +1,2159 @@ +# a glorified C pre-processor parser + +import sys, re, string +from utils import * +from defaults import * + +debugTokens = False +debugDirectiveTokenizer = False +debugLineParsing = False +debugCppExpr = False +debugOptimIf01 = False + +##################################################################################### +##################################################################################### +##### ##### +##### C P P T O K E N S ##### +##### ##### +##################################################################################### +##################################################################################### + +# the list of supported C-preprocessor tokens +# plus a couple of C tokens as well +tokEOF = "\0" +tokLN = "\n" +tokSTRINGIFY = "#" +tokCONCAT = "##" +tokLOGICAND = "&&" +tokLOGICOR = "||" +tokSHL = "<<" +tokSHR = ">>" +tokEQUAL = "==" +tokNEQUAL = "!=" +tokLT = "<" +tokLTE = "<=" +tokGT = ">" +tokGTE = ">=" +tokELLIPSIS = "..." +tokSPACE = " " +tokDEFINED = "defined" +tokLPAREN = "(" +tokRPAREN = ")" +tokNOT = "!" +tokPLUS = "+" +tokMINUS = "-" +tokMULTIPLY = "*" +tokDIVIDE = "/" +tokMODULUS = "%" +tokBINAND = "&" +tokBINOR = "|" +tokBINXOR = "^" +tokCOMMA = "," +tokLBRACE = "{" +tokRBRACE = "}" +tokARROW = "->" +tokINCREMENT = "++" +tokDECREMENT = "--" +tokNUMBER = "<number>" +tokIDENT = "<ident>" +tokSTRING = "<string>" + +class Token: + """a simple class to hold information about a given token. + each token has a position in the source code, as well as + an 'id' and a 'value'. the id is a string that identifies + the token's class, while the value is the string of the + original token itself. + + for example, the tokenizer concatenates a series of spaces + and tabs as a single tokSPACE id, whose value if the original + spaces+tabs sequence.""" + + def __init__(self): + self.id = None + self.value = None + self.lineno = 0 + self.colno = 0 + + def set(self,id,val=None): + self.id = id + if val: + self.value = val + else: + self.value = id + return None + + def copyFrom(self,src): + self.id = src.id + self.value = src.value + self.lineno = src.lineno + self.colno = src.colno + + def __repr__(self): + if self.id == tokIDENT: + return "(ident %s)" % self.value + if self.id == tokNUMBER: + return "(number %s)" % self.value + if self.id == tokSTRING: + return "(string '%s')" % self.value + if self.id == tokLN: + return "<LN>" + if self.id == tokEOF: + return "<EOF>" + if self.id == tokSPACE and self.value == "\\": + # this corresponds to a trailing \ that was transformed into a tokSPACE + return "<\\>" + + return self.id + + def __str__(self): + if self.id == tokIDENT: + return self.value + if self.id == tokNUMBER: + return self.value + if self.id == tokSTRING: + return self.value + if self.id == tokEOF: + return "<EOF>" + if self.id == tokSPACE: + if self.value == "\\": # trailing \ + return "\\\n" + else: + return self.value + + return self.id + +class BadExpectedToken(Exception): + def __init__(self,msg): + print msg + +##################################################################################### +##################################################################################### +##### ##### +##### C P P T O K E N C U R S O R ##### +##### ##### +##################################################################################### +##################################################################################### + +class TokenCursor: + """a small class to iterate over a list of Token objects""" + def __init__(self,tokens): + self.tokens = tokens + self.n = 0 + self.count = len(tokens) + + def set(self,n): + """set the current position""" + if n < 0: + n = 0 + if n > self.count: + n = self.count + self.n = n + + def peekId(self): + """retrieve the id of the current token""" + if (self.n >= self.count): + return None + return self.tokens[self.n].id + + def peek(self): + """retrieve the current token. does not change position""" + if (self.n >= self.count): + return None + return self.tokens[self.n] + + def skip(self): + """increase current token position""" + if (self.n < self.count): + self.n += 1 + + def skipSpaces(self): + """skip over all space tokens, this includes tokSPACE and tokLN""" + while 1: + tok = self.peekId() + if tok != tokSPACE and tok != tokLN: + break + self.skip() + + def skipIfId(self,id): + """skip an optional token""" + if self.peekId() == id: + self.skip() + + def expectId(self,id): + """raise an exception if the current token hasn't a given id. + otherwise skip over it""" + tok = self.peek() + if tok.id != id: + raise BadExpectedToken, "%d:%d: '%s' expected, received '%s'" % (tok.lineno, tok.colno, id, tok.id) + self.skip() + + def remain(self): + """return the list of remaining tokens""" + return self.tokens[self.n:] + + +##################################################################################### +##################################################################################### +##### ##### +##### C P P T O K E N I Z E R ##### +##### ##### +##################################################################################### +##################################################################################### + +# list of long symbols, i.e. those that take more than one characters +cppLongSymbols = [ tokCONCAT, tokLOGICAND, tokLOGICOR, tokSHL, tokSHR, tokELLIPSIS, tokEQUAL,\ + tokNEQUAL, tokLTE, tokGTE, tokARROW, tokINCREMENT, tokDECREMENT ] + +class CppTokenizer: + """an abstract class used to convert some input text into a list + of tokens. real implementations follow and differ in the format + of the input text only""" + + def __init__(self): + """initialize a new CppTokenizer object""" + self.eof = False # end of file reached ? + self.text = None # content of current line, with final \n stripped + self.line = 0 # number of current line + self.pos = 0 # current character position in current line + self.len = 0 # length of current line text + self.held = Token() + + def setLineText(self,line): + """set the content of the (next) current line. should be called + by fillLineText() in derived classes""" + self.text = line + self.len = len(line) + self.pos = 0 + + def fillLineText(self): + """refresh the content of 'line' with a new line of input""" + # to be overriden + self.eof = True + + def markPos(self,tok): + """mark the position of the current token in the source file""" + if self.eof or self.pos > self.len: + tok.lineno = self.line + 1 + tok.colno = 0 + else: + tok.lineno = self.line + tok.colno = self.pos + + def peekChar(self): + """return the current token under the cursor without moving it""" + if self.eof: + return tokEOF + + if self.pos > self.len: + self.pos = 0 + self.line += 1 + self.fillLineText() + if self.eof: + return tokEOF + + if self.pos == self.len: + return tokLN + else: + return self.text[self.pos] + + def peekNChar(self,n): + """try to peek the next n chars on the same line""" + if self.pos + n > self.len: + return None + return self.text[self.pos:self.pos+n] + + def skipChar(self): + """increment the token cursor position""" + if not self.eof: + self.pos += 1 + + def skipNChars(self,n): + if self.pos + n <= self.len: + self.pos += n + else: + while n > 0: + self.skipChar() + n -= 1 + + def nextChar(self): + """retrieve the token at the current cursor position, then skip it""" + result = self.peekChar() + self.skipChar() + return result + + def getEscape(self): + # try to get all characters after a backslash (\) + result = self.nextChar() + if result == "0": + # octal number ? + num = self.peekNChar(3) + if num != None: + isOctal = True + for d in num: + if not d in "01234567": + isOctal = False + break + if isOctal: + result += num + self.skipNChars(3) + elif result == "x" or result == "X": + # hex number ? + num = self.peekNChar(2) + if num != None: + isHex = True + for d in num: + if not d in "012345678abcdefABCDEF": + isHex = False + break + if isHex: + result += num + self.skipNChars(2) + elif result == "u" or result == "U": + # unicode char ? + num = self.peekNChar(4) + if num != None: + isHex = True + for d in num: + if not d in "012345678abcdefABCDEF": + isHex = False + break + if isHex: + result += num + self.skipNChars(4) + + return result + + def nextRealToken(self,tok): + """return next CPP token, used internally by nextToken()""" + c = self.nextChar() + if c == tokEOF or c == tokLN: + return tok.set(c) + + if c == '/': + c = self.peekChar() + if c == '/': # C++ comment line + self.skipChar() + while 1: + c = self.nextChar() + if c == tokEOF or c == tokLN: + break + return tok.set(tokLN) + if c == '*': # C comment start + self.skipChar() + value = "/*" + prev_c = None + while 1: + c = self.nextChar() + if c == tokEOF: + #print "## EOF after '%s'" % value + return tok.set(tokEOF,value) + if c == '/' and prev_c == '*': + break + prev_c = c + value += c + + value += "/" + #print "## COMMENT: '%s'" % value + return tok.set(tokSPACE,value) + c = '/' + + if c.isspace(): + while 1: + c2 = self.peekChar() + if c2 == tokLN or not c2.isspace(): + break + c += c2 + self.skipChar() + return tok.set(tokSPACE,c) + + if c == '\\': + if debugTokens: + print "nextRealToken: \\ found, next token is '%s'" % repr(self.peekChar()) + if self.peekChar() == tokLN: # trailing \ + # eat the tokLN + self.skipChar() + # we replace a trailing \ by a tokSPACE whose value is + # simply "\\". this allows us to detect them later when + # needed. + return tok.set(tokSPACE,"\\") + else: + # treat as a single token here ? + c +=self.getEscape() + return tok.set(c) + + if c == "'": # chars + c2 = self.nextChar() + c += c2 + if c2 == '\\': + c += self.getEscape() + + while 1: + c2 = self.nextChar() + if c2 == tokEOF: + break + c += c2 + if c2 == "'": + break + + return tok.set(tokSTRING, c) + + if c == '"': # strings + quote = 0 + while 1: + c2 = self.nextChar() + if c2 == tokEOF: + return tok.set(tokSTRING,c) + + c += c2 + if not quote: + if c2 == '"': + return tok.set(tokSTRING,c) + if c2 == "\\": + quote = 1 + else: + quote = 0 + + if c >= "0" and c <= "9": # integers ? + while 1: + c2 = self.peekChar() + if c2 == tokLN or (not c2.isalnum() and c2 != "_"): + break + c += c2 + self.skipChar() + return tok.set(tokNUMBER,c) + + if c.isalnum() or c == "_": # identifiers ? + while 1: + c2 = self.peekChar() + if c2 == tokLN or (not c2.isalnum() and c2 != "_"): + break + c += c2 + self.skipChar() + if c == tokDEFINED: + return tok.set(tokDEFINED) + else: + return tok.set(tokIDENT,c) + + # check special symbols + for sk in cppLongSymbols: + if c == sk[0]: + sklen = len(sk[1:]) + if self.pos + sklen <= self.len and \ + self.text[self.pos:self.pos+sklen] == sk[1:]: + self.pos += sklen + return tok.set(sk) + + return tok.set(c) + + def nextToken(self,tok): + """return the next token from the input text. this function + really updates 'tok', and does not return a new one""" + self.markPos(tok) + self.nextRealToken(tok) + + def getToken(self): + tok = Token() + self.nextToken(tok) + if debugTokens: + print "getTokens: %s" % repr(tok) + return tok + + def toTokenList(self): + """convert the input text of a CppTokenizer into a direct + list of token objects. tokEOF is stripped from the result""" + result = [] + while 1: + tok = Token() + self.nextToken(tok) + if tok.id == tokEOF: + break + result.append(tok) + return result + +class CppLineTokenizer(CppTokenizer): + """a CppTokenizer derived class that accepts a single line of text as input""" + def __init__(self,line,lineno=1): + CppTokenizer.__init__(self) + self.line = lineno + self.setLineText(line) + + +class CppLinesTokenizer(CppTokenizer): + """a CppTokenizer derived class that accepts a list of texdt lines as input. + the lines must not have a trailing \n""" + def __init__(self,lines=[],lineno=1): + """initialize a CppLinesTokenizer. you can later add lines using addLines()""" + CppTokenizer.__init__(self) + self.line = lineno + self.lines = lines + self.index = 0 + self.count = len(lines) + + if self.count > 0: + self.fillLineText() + else: + self.eof = True + + def addLine(self,line): + """add a line to a CppLinesTokenizer. this can be done after tokenization + happens""" + if self.count == 0: + self.setLineText(line) + self.index = 1 + self.lines.append(line) + self.count += 1 + self.eof = False + + def fillLineText(self): + if self.index < self.count: + self.setLineText(self.lines[self.index]) + self.index += 1 + else: + self.eof = True + + +class CppFileTokenizer(CppTokenizer): + def __init__(self,file,lineno=1): + CppTokenizer.__init__(self) + self.file = file + self.line = lineno + + def fillLineText(self): + line = self.file.readline() + if len(line) > 0: + if line[-1] == '\n': + line = line[:-1] + if len(line) > 0 and line[-1] == "\r": + line = line[:-1] + self.setLineText(line) + else: + self.eof = True + +# Unit testing +# +class CppTokenizerTester: + """a class used to test CppTokenizer classes""" + def __init__(self,tokenizer=None): + self.tokenizer = tokenizer + self.token = Token() + + def setTokenizer(self,tokenizer): + self.tokenizer = tokenizer + + def expect(self,id): + self.tokenizer.nextToken(self.token) + tokid = self.token.id + if tokid == id: + return + if self.token.value == id and (tokid == tokIDENT or tokid == tokNUMBER): + return + raise BadExpectedToken, "### BAD TOKEN: '%s' expecting '%s'" % (self.token.id,id) + + def expectToken(self,id,line,col): + self.expect(id) + if self.token.lineno != line: + raise BadExpectedToken, "### BAD LINENO: token '%s' got '%d' expecting '%d'" % (id,self.token.lineno,line) + if self.token.colno != col: + raise BadExpectedToken, "### BAD COLNO: '%d' expecting '%d'" % (self.token.colno,col) + + def expectTokenVal(self,id,value,line,col): + self.expectToken(id,line,col) + if self.token.value != value: + raise BadExpectedToken, "### BAD VALUE: '%s' expecting '%s'" % (self.token.value,value) + + def expectList(self,list): + for item in list: + self.expect(item) + +def test_CppTokenizer(): + print "running CppTokenizer tests" + tester = CppTokenizerTester() + + tester.setTokenizer( CppLineTokenizer("#an/example && (01923_xy)") ) + tester.expectList( ["#", "an", "/", "example", tokSPACE, tokLOGICAND, tokSPACE, tokLPAREN, "01923_xy", \ + tokRPAREN, tokLN, tokEOF] ) + + tester.setTokenizer( CppLineTokenizer("FOO(BAR) && defined(BAZ)") ) + tester.expectList( ["FOO", tokLPAREN, "BAR", tokRPAREN, tokSPACE, tokLOGICAND, tokSPACE, + tokDEFINED, tokLPAREN, "BAZ", tokRPAREN, tokLN, tokEOF] ) + + tester.setTokenizer( CppLinesTokenizer( ["/*", "#", "*/"] ) ) + tester.expectList( [ tokSPACE, tokLN, tokEOF ] ) + + tester.setTokenizer( CppLinesTokenizer( ["first", "second"] ) ) + tester.expectList( [ "first", tokLN, "second", tokLN, tokEOF ] ) + + tester.setTokenizer( CppLinesTokenizer( ["first second", " third"] ) ) + tester.expectToken( "first", 1, 0 ) + tester.expectToken( tokSPACE, 1, 5 ) + tester.expectToken( "second", 1, 6 ) + tester.expectToken( tokLN, 1, 12 ) + tester.expectToken( tokSPACE, 2, 0 ) + tester.expectToken( "third", 2, 2 ) + + tester.setTokenizer( CppLinesTokenizer( [ "boo /* what the", "hell */" ] ) ) + tester.expectList( [ "boo", tokSPACE ] ) + tester.expectTokenVal( tokSPACE, "/* what the\nhell */", 1, 4 ) + tester.expectList( [ tokLN, tokEOF ] ) + + tester.setTokenizer( CppLinesTokenizer( [ "an \\", " example" ] ) ) + tester.expectToken( "an", 1, 0 ) + tester.expectToken( tokSPACE, 1, 2 ) + tester.expectTokenVal( tokSPACE, "\\", 1, 3 ) + tester.expectToken( tokSPACE, 2, 0 ) + tester.expectToken( "example", 2, 1 ) + tester.expectToken( tokLN, 2, 8 ) + + return True + + +##################################################################################### +##################################################################################### +##### ##### +##### C P P E X P R E S S I O N S ##### +##### ##### +##################################################################################### +##################################################################################### + +# Cpp expressions are modeled by tuples of the form (op,arg) or (op,arg1,arg2), etc.. +# op is an "operator" string + +class Expr: + """a class used to model a CPP expression""" + opInteger = "int" + opIdent = "ident" + opCall = "call" + opDefined = "defined" + opTest = "?" + opLogicNot = "!" + opNot = "~" + opNeg = "[-]" + opUnaryPlus = "[+]" + opAdd = "+" + opSub = "-" + opMul = "*" + opDiv = "/" + opMod = "%" + opAnd = "&" + opOr = "|" + opXor = "^" + opLogicAnd = "&&" + opLogicOr = "||" + opEqual = "==" + opNotEqual = "!=" + opLess = "<" + opLessEq = "<=" + opGreater = ">" + opGreaterEq = ">=" + opShl = "<<" + opShr = ">>" + + unaries = [ opLogicNot, opNot, opNeg, opUnaryPlus ] + binaries = [ opAdd, opSub, opMul, opDiv, opMod, opAnd, opOr, opXor, opLogicAnd, opLogicOr, + opEqual, opNotEqual, opLess, opLessEq, opGreater, opGreaterEq ] + + precedences = { + opTest: 0, + opLogicOr: 1, + opLogicNot: 2, + opOr : 3, + opXor: 4, + opAnd: 5, + opEqual: 6, opNotEqual: 6, + opLess:7, opLessEq:7, opGreater:7, opGreaterEq:7, + opShl:8, opShr:8, + opAdd:9, opSub:9, + opMul:10, opDiv:10, opMod:10, + opLogicNot:11, + opNot: 12, + } + + def __init__(self,op): + self.op = op + + def __repr__(self): + return "(%s)" % self.op + + def __str__(self): + return "operator(%s)" % self.op + + def precedence(self): + """return the precedence of a given operator""" + return Expr.precedences.get(self.op, 1000) + + def isUnary(self): + return self.op in Expr.unaries + + def isBinary(self): + return self.op in Expr.binaries + + def isDefined(self): + return self.op is opDefined + + def toInt(self): + """return the integer value of a given expression. only valid for integer expressions + will return None otherwise""" + return None + +class IntExpr(Expr): + def __init__(self,value): + Expr.__init__(self,opInteger) + self.arg = value + + def __repr__(self): + return "(int %s)" % self.arg + + def __str__(self): + return self.arg + + def toInt(self): + s = self.arg # string value + # get rid of U or L suffixes + while len(s) > 0 and s[-1] in "LUlu": + s = s[:-1] + return string.atoi(s) + +class IdentExpr(Expr): + def __init__(self,name): + Expr.__init__(self,opIdent) + self.name = name + + def __repr__(self): + return "(ident %s)" % self.name + + def __str__(self): + return self.name + +class CallExpr(Expr): + def __init__(self,funcname,params): + Expr.__init__(self,opCall) + self.funcname = funcname + self.params = params + + def __repr__(self): + result = "(call %s [" % self.funcname + comma = "" + for param in self.params: + result += "%s%s" % (comma, repr(param)) + comma = "," + result += "])" + return result + + def __str__(self): + result = "%s(" % self.funcname + comma = "" + for param in self.params: + result += "%s%s" % (comma, str(param)) + comma = "," + + result += ")" + return result + +class TestExpr(Expr): + def __init__(self,cond,iftrue,iffalse): + Expr.__init__(self,opTest) + self.cond = cond + self.iftrue = iftrue + self.iffalse = iffalse + + def __repr__(self): + return "(?: %s %s %s)" % (repr(self.cond),repr(self.iftrue),repr(self.iffalse)) + + def __str__(self): + return "(%s) ? (%s) : (%s)" % (self.cond, self.iftrue, self.iffalse) + +class SingleArgExpr(Expr): + def __init__(self,op,arg): + Expr.__init__(self,op) + self.arg = arg + + def __repr__(self): + return "(%s %s)" % (self.op, repr(self.arg)) + +class DefinedExpr(SingleArgExpr): + def __init__(self,op,macroname): + SingleArgExpr.__init__(self.opDefined,macroname) + + def __str__(self): + return "defined(%s)" % self.arg + + +class UnaryExpr(SingleArgExpr): + def __init__(self,op,arg,opstr=None): + SingleArgExpr.__init__(self,op,arg) + if not opstr: + opstr = op + self.opstr = opstr + + def __str__(self): + arg_s = str(self.arg) + arg_prec = self.arg.precedence() + self_prec = self.precedence() + if arg_prec < self_prec: + return "%s(%s)" % (self.opstr,arg_s) + else: + return "%s%s" % (self.opstr, arg_s) + +class TwoArgExpr(Expr): + def __init__(self,op,arg1,arg2): + Expr.__init__(self,op) + self.arg1 = arg1 + self.arg2 = arg2 + + def __repr__(self): + return "(%s %s %s)" % (self.op, repr(self.arg1), repr(self.arg2)) + +class BinaryExpr(TwoArgExpr): + def __init__(self,op,arg1,arg2,opstr=None): + TwoArgExpr.__init__(self,op,arg1,arg2) + if not opstr: + opstr = op + self.opstr = opstr + + def __str__(self): + arg1_s = str(self.arg1) + arg2_s = str(self.arg2) + arg1_prec = self.arg1.precedence() + arg2_prec = self.arg2.precedence() + self_prec = self.precedence() + + result = "" + if arg1_prec < self_prec: + result += "(%s)" % arg1_s + else: + result += arg1_s + + result += " %s " % self.opstr + + if arg2_prec < self_prec: + result += "(%s)" % arg2_s + else: + result += arg2_s + + return result + +##################################################################################### +##################################################################################### +##### ##### +##### C P P E X P R E S S I O N P A R S E R ##### +##### ##### +##################################################################################### +##################################################################################### + + +class ExprParser: + """a class used to convert a list of tokens into a cpp Expr object""" + + re_octal = re.compile(r"\s*\(0[0-7]+\).*") + re_decimal = re.compile(r"\s*\(\d+[ulUL]*\).*") + re_hexadecimal = re.compile(r"\s*\(0[xX][0-9a-fA-F]*\).*") + + def __init__(self,tokens): + self.tok = tokens + self.n = len(self.tok) + self.i = 0 + + def mark(self): + return self.i + + def release(self,pos): + self.i = pos + + def peekId(self): + if self.i < self.n: + return self.tok[self.i].id + return None + + def peek(self): + if self.i < self.n: + return self.tok[self.i] + return None + + def skip(self): + if self.i < self.n: + self.i += 1 + + def skipOptional(self,id): + if self.i < self.n and self.tok[self.i].id == id: + self.i += 1 + + def skipSpaces(self): + i = self.i + n = self.n + tok = self.tok + while i < n and (tok[i] == tokSPACE or tok[i] == tokLN): + i += 1 + self.i = i + + # all the isXXX functions returns a (expr,nextpos) pair if a match is found + # or None if not + + def is_integer(self): + id = self.tok[self.i].id + c = id[0] + if c < '0' or c > '9': + return None + + m = ExprParser.re_octal.match(id) + if m: + return (IntExpr(id), m.end(1)) + + m = ExprParser.re_decimal.match(id) + if m: + return (IntExpr(id), m.end(1)) + + m = ExprParser.re_hexadecimal(id) + if m: + return (IntExpr(id), m.end(1)) + + return None + + def is_defined(self): + id = self.tok[self.i].id + if id != "defined": + return None + + pos = self.mark() + + use_paren = 0 + if self.peekId() == tokLPAREN: + self.skip() + use_paren = 1 + + if self.peekId() != tokIDENT: + self.throw( BadExpectedToken, "identifier expected") + + macroname = self.peek().value + self.skip() + if use_paren: + self.skipSpaces() + if self.peekId() != tokRPAREN: + self.throw( BadExpectedToken, "missing right-paren after 'defined' directive") + self.skip() + + i = self.i + return (DefinedExpr(macroname),i+1) + + def is_call_or_ident(self): + pass + + def parse(self, i): + return None + +##################################################################################### +##################################################################################### +##### ##### +##### C P P E X P R E S S I O N S ##### +##### ##### +##################################################################################### +##################################################################################### + +class CppInvalidExpression(Exception): + """an exception raised when an invalid/unsupported cpp expression is detected""" + pass + +class CppExpr: + """a class that models the condition of #if directives into + an expression tree. each node in the tree is of the form (op,arg) or (op,arg1,arg2) + where "op" is a string describing the operation""" + + unaries = [ "!", "~" ] + binaries = [ "+", "-", "<", "<=", ">=", ">", "&&", "||", "*", "/", "%", "&", "|", "^", "<<", ">>", "==", "!=" ] + precedences = { "||": 1, + "&&": 2, + "|": 3, + "^": 4, + "&": 5, + "==":6, "!=":6, + "<":7, "<=":7, ">":7, ">=":7, + "<<":8, ">>":8, + "+":9, "-":9, + "*":10, "/":10, "%":10, + "!":11, "~":12 + } + + def __init__(self, tokens): + """initialize a CppExpr. 'tokens' must be a CppToken list""" + self.tok = tokens + self.n = len(tokens) + if debugCppExpr: + print "CppExpr: trying to parse %s" % repr(tokens) + expr = self.is_expr(0) + if debugCppExpr: + print "CppExpr: got " + repr(expr) + self.expr = expr[0] + + re_cpp_constant = re.compile(r"((\d|\w|_)+)") + + def throw(self,exception,i,msg): + if i < self.n: + tok = self.tok[i] + print "%d:%d: %s" % (tok.lineno,tok.colno,msg) + else: + print "EOF: %s" % msg + raise exception + + def skip_spaces(self,i): + """skip spaces in input token list""" + while i < self.n: + t = self.tok[i] + if t.id != tokSPACE and t.id != tokLN: + break + i += 1 + return i + + def expectId(self,i,id): + """check that a given token id is at the current position, then skip over it""" + i = self.skip_spaces(i) + if i >= self.n or self.tok[i].id != id: + self.throw(BadExpectedToken,i,"### expecting '%s' in expression, got '%s'" % (id, self.tok[i].id)) + return i+1 + + def expectIdent(self,i): + i = self.skip_spaces(i) + if i >= self.n or self.tok[i].id != tokIDENT: + self.throw(BadExpectedToken,i,"### expecting identifier in expression, got '%s'" % (id, self.tok[i].id)) + return i+1 + + # the is_xxxxx function returns either None or a pair (e,nextpos) + # where 'e' is an expression tuple (e.g. (op,arg)) and 'nextpos' is + # the corresponding next position in the input token list + # + + def is_decimal(self,i): + v = self.tok[i].value[:] + while len(v) > 0 and v[-1] in "ULul": + v = v[:-1] + for digit in v: + if not digit.isdigit(): + return None + + # for an integer expression tuple, the argument + # is simply the value as an integer + val = string.atoi(v) + return ("int", val), i+1 + + def is_hexadecimal(self,i): + v = self.tok[i].value[:] + while len(v) > 0 and v[-1] in "ULul": + v = v[:-1] + if len(v) > 2 and (v[0:2] == "0x" or v[0:2] == "0X"): + for digit in v[2:]: + if not digit in "0123456789abcdefABCDEF": + return None + + # for an hex expression tuple, the argument + # is the value as an integer + val = int(v[2:], 16) + return ("hex", val), i+1 + + return None + + def is_integer(self,i): + if self.tok[i].id != tokNUMBER: + return None + + c = self.is_decimal(i) + if c: return c + + c = self.is_hexadecimal(i) + if c: return c + + return None + + def is_number(self,i): + t = self.tok[i] + if t.id == tokMINUS and i+1 < self.n: + c = self.is_integer(i+1) + if c: + e, i2 = c + op, val = e + return (op, -val), i2 + if t.id == tokPLUS and i+1 < self.n: + c = self.is_integer(i+1) + if c: return c + + return self.is_integer(i) + + + def is_alnum(self,i): + """test wether a given token is alpha-numeric""" + i = self.skip_spaces(i) + if i >= self.n: + return None + t = self.tok[i] + m = CppExpr.re_cpp_constant.match(t.id) + if m: + #print "... alnum '%s'" % m.group(1) + r = m.group(1) + return ("ident", r), i+1 + return None + + def is_defined(self,i): + t = self.tok[i] + if t.id != tokDEFINED: + return None + + # we have the defined keyword, check the rest + i = self.skip_spaces(i+1) + use_parens = 0 + if i < self.n and self.tok[i].id == tokLPAREN: + use_parens = 1 + i = self.skip_spaces(i+1) + + if i >= self.n: + self.throw(CppConstantExpected,i,"### 'defined' must be followed by macro name or left paren") + + t = self.tok[i] + if t.id != tokIDENT: + self.throw(CppConstantExpected,i,"### 'defined' must be followed by macro name") + + i += 1 + if use_parens: + i = self.expectId(i,tokRPAREN) + + return ("defined",t.value), i + + + def is_call_or_ident(self,i): + i = self.skip_spaces(i) + if i >= self.n: + return None + + t = self.tok[i] + if t.id != tokIDENT: + return None + + name = t.value + + i = self.skip_spaces(i+1) + if i >= self.n or self.tok[i].id != tokLPAREN: + return ("ident", name), i + + params = [] + depth = 1 + i += 1 + j = i + while i < self.n: + id = self.tok[i].id + if id == tokLPAREN: + depth += 1 + elif depth == 1 and (id == tokCOMMA or id == tokRPAREN): + while j < i and self.tok[j].id == tokSPACE: + j += 1 + k = i + while k > j and self.tok[k-1].id == tokSPACE: + k -= 1 + param = self.tok[j:k] + params.append( param ) + if id == tokRPAREN: + break + j = i+1 + elif id == tokRPAREN: + depth -= 1 + i += 1 + + if i >= self.n: + return None + + return ("call", (name, params)), i+1 + + def is_token(self,i,token): + i = self.skip_spaces(i) + if i >= self.n or self.tok[i].id != token: + return None + return token, i+1 + + + def is_value(self,i): + t = self.tok[i] + if t.id == tokSTRING: + return ("string", t.value), i+1 + + c = self.is_number(i) + if c: return c + + c = self.is_defined(i) + if c: return c + + c = self.is_call_or_ident(i) + if c: return c + + i = self.skip_spaces(i) + if i >= self.n or self.tok[i].id != tokLPAREN: + return None + + popcount = 1 + i2 = i+1 + while i2 < self.n: + t = self.tok[i2] + if t.id == tokLPAREN: + popcount += 1 + elif t.id == tokRPAREN: + popcount -= 1 + if popcount == 0: + break + i2 += 1 + + if popcount != 0: + self.throw(CppInvalidExpression, i, "expression missing closing parenthesis") + + if debugCppExpr: + print "CppExpr: trying to parse sub-expression %s" % repr(self.tok[i+1:i2]) + oldcount = self.n + self.n = i2 + c = self.is_expr(i+1) + self.n = oldcount + if not c: + self.throw(CppInvalidExpression, i, "invalid expression within parenthesis") + + e, i = c + return e, i2+1 + + def is_unary(self,i): + i = self.skip_spaces(i) + if i >= self.n: + return None + + t = self.tok[i] + if t.id in CppExpr.unaries: + c = self.is_unary(i+1) + if not c: + self.throw(CppInvalidExpression, i, "%s operator must be followed by value" % t.id) + e, i = c + return (t.id, e), i + + return self.is_value(i) + + def is_binary(self,i): + i = self.skip_spaces(i) + if i >= self.n: + return None + + c = self.is_unary(i) + if not c: + return None + + e1, i2 = c + i2 = self.skip_spaces(i2) + if i2 >= self.n: + return c + + t = self.tok[i2] + if t.id in CppExpr.binaries: + c = self.is_binary(i2+1) + if not c: + self.throw(CppInvalidExpression, i,"### %s operator must be followed by value" % t.id ) + e2, i3 = c + return (t.id, e1, e2), i3 + + return None + + def is_expr(self,i): + return self.is_binary(i) + + def dump_node(self,e): + op = e[0] + line = "(" + op + if op == "int": + line += " %d)" % e[1] + elif op == "hex": + line += " 0x%x)" % e[1] + elif op == "ident": + line += " %s)" % e[1] + elif op == "defined": + line += " %s)" % e[1] + elif op == "call": + arg = e[1] + line += " %s [" % arg[0] + prefix = "" + for param in arg[1]: + par = "" + for tok in param: + par += str(tok) + line += "%s%s" % (prefix, par) + prefix = "," + line += "])" + elif op in CppExpr.unaries: + line += " %s)" % self.dump_node(e[1]) + elif op in CppExpr.binaries: + line += " %s %s)" % (self.dump_node(e[1]), self.dump_node(e[2])) + else: + line += " ?%s)" % repr(e[1]) + + return line + + def __repr__(self): + return self.dump_node(self.expr) + + def source_node(self,e): + op = e[0] + if op == "int": + return "%d" % e[1] + if op == "hex": + return "0x%x" % e[1] + if op == "ident": + # XXX: should try to expand + return e[1] + if op == "defined": + return "defined(%s)" % e[1] + + prec = CppExpr.precedences.get(op,1000) + arg = e[1] + if op in CppExpr.unaries: + arg_src = self.source_node(arg) + arg_op = arg[0] + arg_prec = CppExpr.precedences.get(arg[0],1000) + if arg_prec < prec: + return "!(" + arg_src + ")" + else: + return "!" + arg_src + if op in CppExpr.binaries: + arg2 = e[2] + arg1_op = arg[0] + arg2_op = arg2[0] + arg1_src = self.source_node(arg) + arg2_src = self.source_node(arg2) + if CppExpr.precedences.get(arg1_op,1000) < prec: + arg1_src = "(%s)" % arg1_src + if CppExpr.precedences.get(arg2_op,1000) < prec: + arg2_src = "(%s)" % arg2_src + + return "%s %s %s" % (arg1_src, op, arg2_src) + return "???" + + def __str__(self): + return self.source_node(self.expr) + + def int_node(self,e): + if e[0] == "int": + return e[1] + elif e[1] == "hex": + return int(e[1],16) + else: + return None + + def toInt(self): + return self.int_node(self.expr) + + def optimize_node(self,e,macros={}): + op = e[0] + if op == "defined": + name = e[1] + if macros.has_key(name): + if macros[name] == kCppUndefinedMacro: + return ("int", 0) + else: + return ("int", 1) + + if kernel_remove_config_macros and name.startswith("CONFIG_"): + return ("int", 0) + + elif op == "!": + op, v = e + v = self.optimize_node(v, macros) + if v[0] == "int": + if v[1] == 0: + return ("int", 1) + else: + return ("int", 0) + + elif op == "&&": + op, l, r = e + l = self.optimize_node(l, macros) + r = self.optimize_node(r, macros) + li = self.int_node(l) + ri = self.int_node(r) + if li != None: + if li == 0: + return ("int", 0) + else: + return r + + elif op == "||": + op, l, r = e + l = self.optimize_node(l, macros) + r = self.optimize_node(r, macros) + li = self.int_node(l) + ri = self.int_node(r) + if li != None: + if li == 0: + return r + else: + return ("int", 1) + elif ri != None: + if ri == 0: + return l + else: + return ("int", 1) + return e + + def optimize(self,macros={}): + self.expr = self.optimize_node(self.expr,macros) + + def removePrefixedNode(self,e,prefix,names): + op = e[0] + if op == "defined": + name = e[1] + if name.startswith(prefix): + if names.has_key[name] and names[name] == "y": + return ("int", 1) + else: + return ("int", 0) + + elif op in CppExpr.unaries: + op, v = e + v = self.removePrefixedNode(v,prefix,names) + return (op, v) + elif op in CppExpr.binaries: + op, v1, v2 = e + v1 = self.removePrefixedNode(v1,prefix,names) + v2 = self.removePrefixedNode(v2,prefix,names) + return (op, v1, v2) + elif op == "call": + func, params = e[1] + params2 = [] + for param in params: + params2.append( self.removePrefixedNode(param,prefix,names) ) + return (op, (func, params2)) + + return e + + def removePrefixed(self,prefix,names={}): + self.expr = self.removePrefixedNode(self.expr,prefix,names) + + def is_equal_node(self,e1,e2): + if e1[0] != e2[0] or len(e1) != len(e2): + return False + + op = e1[0] + if op == "int" or op == "hex" or op == "!" or op == "defined": + return e1[0] == e2[0] + + return self.is_equal_node(e1[1],e2[1]) and self.is_equal_node(e1[2],e2[2]) + + def is_equal(self,other): + return self.is_equal_node(self.expr,other.expr) + +def test_cpp_expr(expr, expected): + e = CppExpr( CppLineTokenizer( expr ).toTokenList() ) + #print repr(e.expr) + s1 = repr(e) + if s1 != expected: + print "KO: expression '%s' generates '%s', should be '%s'" % (expr, s1, expected) + else: + #print "OK: expression '%s'" % expr + pass + +def test_cpp_expr_optim(expr, expected, macros={}): + e = CppExpr( CppLineTokenizer( expr ).toTokenList() ) + e.optimize(macros) + + s1 = repr(e) + if s1 != expected: + print "KO: optimized expression '%s' generates '%s', should be '%s'" % (expr, s1, expected) + else: + #print "OK: optmized expression '%s'" % expr + pass + +def test_cpp_expr_source(expr, expected): + e = CppExpr( CppLineTokenizer( expr ).toTokenList() ) + s1 = str(e) + if s1 != expected: + print "KO: source expression '%s' generates '%s', should be '%s'" % (expr, s1, expected) + else: + #print "OK: source expression '%s'" % expr + pass + +def test_CppExpr(): + print "testing CppExpr" + test_cpp_expr( "0", "(int 0)" ) + test_cpp_expr( "1", "(int 1)" ) + test_cpp_expr( "1 && 1", "(&& (int 1) (int 1))" ) + test_cpp_expr( "1 && 0", "(&& (int 1) (int 0))" ) + test_cpp_expr( "EXAMPLE", "(ident EXAMPLE)" ) + test_cpp_expr( "EXAMPLE - 3", "(- (ident EXAMPLE) (int 3))" ) + test_cpp_expr( "defined(EXAMPLE)", "(defined EXAMPLE)" ) + test_cpp_expr( "!defined(EXAMPLE)", "(! (defined EXAMPLE))" ) + test_cpp_expr( "defined(ABC) || defined(BINGO)", "(|| (defined ABC) (defined BINGO))" ) + test_cpp_expr( "FOO(BAR)", "(call FOO [BAR])" ) + + test_cpp_expr_optim( "0", "(int 0)" ) + test_cpp_expr_optim( "1", "(int 1)" ) + test_cpp_expr_optim( "1 && 1", "(int 1)" ) + test_cpp_expr_optim( "1 && 0", "(int 0)" ) + test_cpp_expr_optim( "0 && 1", "(int 0)" ) + test_cpp_expr_optim( "0 && 0", "(int 0)" ) + test_cpp_expr_optim( "1 || 1", "(int 1)" ) + test_cpp_expr_optim( "1 || 0", "(int 1)" ) + test_cpp_expr_optim( "0 || 1", "(int 1)" ) + test_cpp_expr_optim( "0 || 0", "(int 0)" ) + test_cpp_expr_optim( "EXAMPLE", "(ident EXAMPLE)" ) + test_cpp_expr_optim( "EXAMPLE - 3", "(- (ident EXAMPLE) (int 3))" ) + test_cpp_expr_optim( "defined(EXAMPLE)", "(defined EXAMPLE)" ) + test_cpp_expr_optim( "defined(EXAMPLE)", "(int 1)", { "EXAMPLE": "XOWOE" } ) + test_cpp_expr_optim( "defined(EXAMPLE)", "(int 0)", { "EXAMPLE": kCppUndefinedMacro} ) + test_cpp_expr_optim( "!defined(EXAMPLE)", "(! (defined EXAMPLE))" ) + test_cpp_expr_optim( "!defined(EXAMPLE)", "(int 0)", { "EXAMPLE" : "XOWOE" } ) + test_cpp_expr_optim( "!defined(EXAMPLE)", "(int 1)", { "EXAMPLE" : kCppUndefinedMacro } ) + test_cpp_expr_optim( "defined(ABC) || defined(BINGO)", "(|| (defined ABC) (defined BINGO))" ) + test_cpp_expr_optim( "defined(ABC) || defined(BINGO)", "(int 1)", { "ABC" : "1" } ) + test_cpp_expr_optim( "defined(ABC) || defined(BINGO)", "(int 1)", { "BINGO" : "1" } ) + test_cpp_expr_optim( "defined(ABC) || defined(BINGO)", "(defined ABC)", { "BINGO" : kCppUndefinedMacro } ) + test_cpp_expr_optim( "defined(ABC) || defined(BINGO)", "(int 0)", { "ABC" : kCppUndefinedMacro, "BINGO" : kCppUndefinedMacro } ) + + test_cpp_expr_source( "0", "0" ) + test_cpp_expr_source( "1", "1" ) + test_cpp_expr_source( "1 && 1", "1 && 1" ) + test_cpp_expr_source( "1 && 0", "1 && 0" ) + test_cpp_expr_source( "0 && 1", "0 && 1" ) + test_cpp_expr_source( "0 && 0", "0 && 0" ) + test_cpp_expr_source( "1 || 1", "1 || 1" ) + test_cpp_expr_source( "1 || 0", "1 || 0" ) + test_cpp_expr_source( "0 || 1", "0 || 1" ) + test_cpp_expr_source( "0 || 0", "0 || 0" ) + test_cpp_expr_source( "EXAMPLE", "EXAMPLE" ) + test_cpp_expr_source( "EXAMPLE - 3", "EXAMPLE - 3" ) + test_cpp_expr_source( "defined(EXAMPLE)", "defined(EXAMPLE)" ) + test_cpp_expr_source( "defined EXAMPLE", "defined(EXAMPLE)" ) + + +##################################################################################### +##################################################################################### +##### ##### +##### C P P B L O C K ##### +##### ##### +##################################################################################### +##################################################################################### + +class Block: + """a class used to model a block of input source text. there are two block types: + - direcive blocks: contain the tokens of a single pre-processor directive (e.g. #if) + - text blocks, contain the tokens of non-directive blocks + + the cpp parser class below will transform an input source file into a list of Block + objects (grouped in a BlockList object for convenience)""" + + def __init__(self,tokens,directive=None,lineno=0): + """initialize a new block, if 'directive' is None, this is a text block + NOTE: this automatically converts '#ifdef MACRO' into '#if defined(MACRO)' + and '#ifndef MACRO' into '#if !defined(MACRO)'""" + if directive == "ifdef": + tok = Token() + tok.set(tokDEFINED) + tokens = [ tok ] + tokens + directive = "if" + + elif directive == "ifndef": + tok1 = Token() + tok2 = Token() + tok1.set(tokNOT) + tok2.set(tokDEFINED) + tokens = [ tok1, tok2 ] + tokens + directive = "if" + + self.tokens = tokens + self.directive = directive + if lineno > 0: + self.lineno = lineno + else: + self.lineno = self.tokens[0].lineno + + if self.isIf(): + self.expr = CppExpr( self.tokens ) + + def isDirective(self): + """returns True iff this is a directive block""" + return self.directive != None + + def isConditional(self): + """returns True iff this is a conditional directive block""" + return self.directive in ["if","ifdef","ifndef","else","elif","endif"] + + def isIf(self): + """returns True iff this is an #if-like directive block""" + return self.directive in ["if","ifdef","ifndef","elif"] + + def isInclude(self): + """checks wether this is a #include directive. if true, then returns the + corresponding file name (with brackets or double-qoutes). None otherwise""" + if self.directive != "include": + return None + + #print "iii " + repr(self.tokens) + if self.tokens[0].id == tokSTRING: + # a double-quote include, that's easy + return self.tokens[0].value + + # we only want the bracket part, not any comments or junk after it + if self.tokens[0].id == "<": + i = 0 + tok = self.tokens + n = len(tok) + while i < n and tok[i].id != ">": + i += 1 + + if i >= n: + return None + + return string.join([ str(x) for x in tok[:i+1] ],"") + + else: + return None + + def __repr__(self): + """generate the representation of a given block""" + if self.directive: + result = "#%s " % self.directive + if self.isIf(): + result += repr(self.expr) + else: + for tok in self.tokens: + result += repr(tok) + else: + result = "" + for tok in self.tokens: + result += repr(tok) + + return result + + def __str__(self): + """generate the string representation of a given block""" + if self.directive: + if self.directive == "if": + # small optimization to re-generate #ifdef and #ifndef + e = self.expr.expr + op = e[0] + if op == "defined": + result = "#ifdef %s" % e[1] + elif op == "!" and e[1][0] == "defined": + result = "#ifndef %s" % e[1][1] + else: + result = "#if " + str(self.expr) + else: + result = "#%s" % self.directive + if len(self.tokens): + result += " " + for tok in self.tokens: + result += str(tok) + else: + result = "" + for tok in self.tokens: + result += str(tok) + + return result + + +class BlockList: + """a convenience class used to hold and process a list of blocks returned by + the cpp parser""" + def __init__(self,blocks): + self.blocks = blocks + + def __len__(self): + return len(self.blocks) + + def __getitem__(self,n): + return self.blocks[n] + + def __repr__(self): + return repr(self.blocks) + + def __str__(self): + result = "" + for b in self.blocks: + result += str(b) + if b.isDirective(): + result += '\n' + return result + + def optimizeIf01(self): + """remove the code between #if 0 .. #endif in a BlockList""" + self.blocks = optimize_if01(self.blocks) + + def optimizeMacros(self, macros): + """remove known defined and undefined macros from a BlockList""" + for b in self.blocks: + if b.isIf(): + b.expr.optimize(macros) + + def removePrefixed(self,prefix,names): + for b in self.blocks: + if b.isIf(): + b.expr.removePrefixed(prefix,names) + + def optimizeAll(self,macros): + self.optimizeMacros(macros) + self.optimizeIf01() + return + + def findIncludes(self): + """return the list of included files in a BlockList""" + result = [] + for b in self.blocks: + i = b.isInclude() + if i: + result.append(i) + + return result + + + def write(self,out): + out.write(str(self)) + + def removeComments(self): + for b in self.blocks: + for tok in b.tokens: + if tok.id == tokSPACE: + tok.value = " " + + def removeEmptyLines(self): + # state = 1 => previous line was tokLN + # state = 0 => previous line was directive + state = 1 + for b in self.blocks: + if b.isDirective(): + #print "$$$ directive %s" % str(b) + state = 0 + else: + # a tokLN followed by spaces is replaced by a single tokLN + # several successive tokLN are replaced by a single one + # + dst = [] + src = b.tokens + n = len(src) + i = 0 + #print "$$$ parsing %s" % repr(src) + while i < n: + # find final tokLN + j = i + while j < n and src[j].id != tokLN: + j += 1 + + if j >= n: + # uhhh + dst += src[i:] + break + + if src[i].id == tokSPACE: + k = i+1 + while src[k].id == tokSPACE: + k += 1 + + if k == j: # empty lines with spaces in it + i = j # remove the spaces + + if i == j: + # an empty line + if state == 1: + i += 1 # remove it + else: + state = 1 + dst.append(src[i]) + i += 1 + else: + # this line is not empty, remove trailing spaces + k = j + while k > i and src[k-1].id == tokSPACE: + k -= 1 + + nn = i + while nn < k: + dst.append(src[nn]) + nn += 1 + dst.append(src[j]) + state = 0 + i = j+1 + + b.tokens = dst + + def removeVarsAndFuncs(self,knownStatics=set()): + """remove all extern and static declarations corresponding + to variable and function declarations. we only accept typedefs + and enum/structs/union declarations. + + however, we keep the definitions corresponding to the set + of known static inline functions in the set 'knownStatics', + which is useful for optimized byteorder swap functions and + stuff like that. + """ + # state = 1 => typedef/struct encountered + # state = 2 => vars or func declaration encountered, skipping until ";" + # state = 0 => normal (i.e. LN + spaces) + state = 0 + depth = 0 + blocks2 = [] + for b in self.blocks: + if b.isDirective(): + blocks2.append(b) + else: + n = len(b.tokens) + i = 0 + first = 0 + if state == 2: + first = n + while i < n: + tok = b.tokens[i] + if state == 0: + bad = 0 + if tok.id in [tokLN, tokSPACE]: + pass + elif tok.value in [ 'struct', 'typedef', 'enum', 'union' ]: + state = 1 + else: + if tok.value in [ 'static', 'extern', '__KINLINE' ]: + j = i+1 + ident = "" + while j < n and not (b.tokens[j].id in [ '(', ';' ]): + if b.tokens[j].id == tokIDENT: + ident = b.tokens[j].value + j += 1 + if j < n and ident in knownStatics: + # this is a known static, we're going to keep its + # definition in the final output + state = 1 + else: + #print "### skip static '%s'" % ident + pass + + if state == 0: + if i > first: + #print "### intermediate from '%s': '%s'" % (tok.value, repr(b.tokens[first:i])) + blocks2.append( Block(b.tokens[first:i]) ) + state = 2 + first = n + + else: # state > 0 + if tok.id == '{': + depth += 1 + + elif tok.id == '}': + if depth > 0: + depth -= 1 + + elif depth == 0 and tok.id == ';': + if state == 2: + first = i+1 + state = 0 + + i += 1 + + if i > first: + #print "### final '%s'" % repr(b.tokens[first:i]) + blocks2.append( Block(b.tokens[first:i]) ) + + self.blocks = blocks2 + + def insertDisclaimer(self,disclaimer="/* auto-generated file, DO NOT EDIT */"): + """insert your standard issue disclaimer that this is an + auto-generated file, etc..""" + tokens = CppLineTokenizer( disclaimer ).toTokenList() + tokens = tokens[:-1] # remove trailing tokLN + self.blocks = [ Block(tokens) ] + self.blocks + +class BlockParser: + """a class used to convert an input source file into a BlockList object""" + + def __init__(self,tokzer=None): + """initialize a block parser. the input source is provided through a Tokenizer + object""" + self.reset(tokzer) + + def reset(self,tokzer): + self.state = 1 + self.tokzer = tokzer + + def getBlocks(self,tokzer=None): + """tokenize and parse the input source, return a BlockList object + NOTE: empty and line-numbering directives are ignored and removed + from the result. as a consequence, it is possible to have + two successive text blocks in the result""" + # state 0 => in source code + # state 1 => in source code, after a LN + # state 2 => in source code, after LN then some space + state = 1 + lastLN = 0 + current = [] + blocks = [] + + if tokzer == None: + tokzer = self.tokzer + + while 1: + tok = tokzer.getToken() + if tok.id == tokEOF: + break + + if tok.id == tokLN: + state = 1 + current.append(tok) + lastLN = len(current) + + elif tok.id == tokSPACE: + if state == 1: + state = 2 + current.append(tok) + + elif tok.id == "#": + if state > 0: + # this is the start of a directive + + if lastLN > 0: + # record previous tokens as text block + block = Block(current[:lastLN]) + blocks.append(block) + lastLN = 0 + + current = [] + + # skip spaces after the # + while 1: + tok = tokzer.getToken() + if tok.id != tokSPACE: + break + + if tok.id != tokIDENT: + # empty or line-numbering, ignore it + if tok.id != tokLN and tok.id != tokEOF: + while 1: + tok = tokzer.getToken() + if tok.id == tokLN or tok.id == tokEOF: + break + continue + + directive = tok.value + lineno = tok.lineno + + # skip spaces + tok = tokzer.getToken() + while tok.id == tokSPACE: + tok = tokzer.getToken() + + # then record tokens until LN + dirtokens = [] + while tok.id != tokLN and tok.id != tokEOF: + dirtokens.append(tok) + tok = tokzer.getToken() + + block = Block(dirtokens,directive,lineno) + blocks.append(block) + state = 1 + + else: + state = 0 + current.append(tok) + + if len(current) > 0: + block = Block(current) + blocks.append(block) + + return BlockList(blocks) + + def parse(self,tokzer): + return self.getBlocks( tokzer ) + + def parseLines(self,lines): + """parse a list of text lines into a BlockList object""" + return self.getBlocks( CppLinesTokenizer(lines) ) + + def parseFile(self,path): + """parse a file into a BlockList object""" + file = open(path, "rt") + result = self.getBlocks( CppFileTokenizer(file) ) + file.close() + return result + + +def test_block_parsing(lines,expected): + blocks = BlockParser().parse( CppLinesTokenizer(lines) ) + if len(blocks) != len(expected): + raise BadExpectedToken, "parser.buildBlocks returned '%s' expecting '%s'" \ + % (str(blocks), repr(expected)) + for n in range(len(blocks)): + if str(blocks[n]) != expected[n]: + raise BadExpectedToken, "parser.buildBlocks()[%d] is '%s', expecting '%s'" \ + % (n, str(blocks[n]), expected[n]) + #for block in blocks: + # print block + +def test_BlockParser(): + test_block_parsing(["#error hello"],["#error hello"]) + test_block_parsing([ "foo", "", "bar" ], [ "foo\n\nbar\n" ]) + test_block_parsing([ "foo", " # ", "bar" ], [ "foo\n","bar\n" ]) + test_block_parsing(\ + [ "foo", " # ", " # /* ahah */ if defined(__KERNEL__) ", "bar", "#endif" ], + [ "foo\n", "#ifdef __KERNEL__", "bar\n", "#endif" ] ) + + +##################################################################################### +##################################################################################### +##### ##### +##### B L O C K L I S T O P T I M I Z A T I O N ##### +##### ##### +##################################################################################### +##################################################################################### + +def find_matching_endif( blocks, i ): + n = len(blocks) + depth = 1 + while i < n: + if blocks[i].isDirective(): + dir = blocks[i].directive + if dir in [ "if", "ifndef", "ifdef" ]: + depth += 1 + elif depth == 1 and dir in [ "else", "elif" ]: + return i + elif dir == "endif": + depth -= 1 + if depth == 0: + return i + i += 1 + return i + +def optimize_if01( blocks ): + """remove the code between #if 0 .. #endif in a list of CppBlocks""" + i = 0 + n = len(blocks) + result = [] + while i < n: + j = i + while j < n and not blocks[j].isIf(): + j += 1 + if j > i: + D2("appending lines %d to %d" % (blocks[i].lineno, blocks[j-1].lineno)) + result += blocks[i:j] + if j >= n: + break + expr = blocks[j].expr + r = expr.toInt() + if r == None: + result.append(blocks[j]) + i = j + 1 + continue + + if r == 0: + # if 0 => skip everything until the corresponding #endif + j = find_matching_endif( blocks, j+1 ) + if j >= n: + # unterminated #if 0, finish here + break + dir = blocks[j].directive + if dir == "endif": + D2("remove 'if 0' .. 'endif' (lines %d to %d)" % (blocks[i].lineno, blocks[j].lineno)) + i = j + 1 + elif dir == "else": + # convert 'else' into 'if 1' + D2("convert 'if 0' .. 'else' into 'if 1' (lines %d to %d)" % (blocks[i].lineno, blocks[j-1].lineno)) + blocks[j].directive = "if" + blocks[j].expr = CppExpr( CppLineTokenizer("1").toTokenList() ) + i = j + elif dir == "elif": + # convert 'elif' into 'if' + D2("convert 'if 0' .. 'elif' into 'if'") + blocks[j].directive = "if" + i = j + continue + + # if 1 => find corresponding endif and remove/transform them + k = find_matching_endif( blocks, j+1 ) + if k >= n: + # unterminated #if 1, finish here + D2("unterminated 'if 1'") + result += blocks[j+1:k] + break + + dir = blocks[k].directive + if dir == "endif": + D2("convert 'if 1' .. 'endif' (lines %d to %d)" % (blocks[j].lineno, blocks[k].lineno)) + result += optimize_if01(blocks[j+1:k]) + i = k+1 + elif dir == "else": + # convert 'else' into 'if 0' + D2("convert 'if 1' .. 'else' (lines %d to %d)" % (blocks[j].lineno, blocks[k].lineno)) + result += optimize_if01(blocks[j+1:k]) + blocks[k].directive = "if" + blocks[k].expr = CppExpr( CppLineTokenizer("0").toTokenList() ) + i = k + elif dir == "elif": + # convert 'elif' into 'if 0' + D2("convert 'if 1' .. 'elif' (lines %d to %d)" % (blocks[j].lineno, blocks[k].lineno)) + result += optimize_if01(blocks[j+1:k]) + blocks[k].expr = CppExpr( CppLineTokenizer("0").toTokenList() ) + i = k + return result + +def test_optimizeAll(): + text = """\ +#if 1 +#define GOOD_1 +#endif +#if 0 +#define BAD_2 +#define BAD_3 +#endif + +#if 1 +#define GOOD_2 +#else +#define BAD_4 +#endif + +#if 0 +#define BAD_5 +#else +#define GOOD_3 +#endif + +#if 0 +#if 1 +#define BAD_6 +#endif +#endif\ +""" + + expected = """\ +#define GOOD_1 + +#define GOOD_2 + +#define GOOD_3 + +""" + + print "running test_BlockList.optimizeAll" + out = StringOutput() + lines = string.split(text, '\n') + list = BlockParser().parse( CppLinesTokenizer(lines) ) + #D_setlevel(2) + list.optimizeAll( {"__KERNEL__":kCppUndefinedMacro} ) + #print repr(list) + list.write(out) + if out.get() != expected: + print "KO: macro optimization failed\n" + print "<<<< expecting '", + print expected, + print "'\n>>>> result '" + print out.get(), + print "'\n----" + + +##################################################################################### +##################################################################################### +##### ##### +##### ##### +##### ##### +##################################################################################### +##################################################################################### + +def runUnitTests(): + """run all unit tests for this program""" + print "running unit tests" + test_CppTokenizer() + test_CppExpr() + test_optimizeAll() + test_BlockParser() + print "OK" + +if __name__ == "__main__": + runUnitTests() |