blob: ce93b5c9179f4129b99697c27c130a33af8538b4 [file] [log] [blame]
# *************************************************************
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# *************************************************************
import sys, os.path
from globals import *
import macroparser
class EOF(Exception):
def __init__ (self):
pass
def str (self):
return "end of file"
class BOF(Exception):
def __init__ (self):
pass
def str (self):
return "beginning of file"
def removeHeaderQuotes (orig):
if len(orig) <= 2:
return orig
elif orig[0] == orig[-1] == '"':
return orig[1:-1]
elif orig[0] == '<' and orig[-1] == '>':
return orig[1:-1]
return orig
def dumpTokens (tokens, toError=False):
scope = 0
indent = " "
line = ''
chars = ''
for token in tokens:
if token in '{<':
if len(line) > 0:
chars += indent*scope + line + "\n"
line = ''
chars += indent*scope + token + "\n"
scope += 1
elif token in '}>':
if len(line) > 0:
chars += indent*scope + line + "\n"
line = ''
scope -= 1
chars += indent*scope + token
elif token == ';':
if len(line) > 0:
chars += indent*scope + line + ";\n"
line = ''
else:
chars += ";\n"
elif len(token) > 0:
line += token + ' '
if len(line) > 0:
chars += line
chars += "\n"
if toError:
sys.stderr.write(chars)
else:
sys.stdout.write(chars)
class HeaderData(object):
def __init__ (self):
self.defines = {}
self.tokens = []
class SrcLexer(object):
"""Lexicographical analyzer for .src format.
The role of a lexer is to parse the source file and break it into
appropriate tokens. Such tokens are later passed to a parser to
build the syntax tree.
"""
headerCache = {}
VISIBLE = 0
INVISIBLE_PRE = 1
INVISIBLE_POST = 2
def __init__ (self, chars, filepath = None):
self.filepath = filepath
self.parentLexer = None
self.chars = chars
self.bufsize = len(self.chars)
# TODO: use parameters for this
# Properties that can be copied.
self.headerDict = dict ()
self.debug = False
self.debugMacro = False
self.includeDirs = list ()
self.expandHeaders = True
self.inMacroDefine = False
self.stopOnHeader = False
def copyProperties (self, other):
"""Copy properties from another instance of SrcLexer."""
# TODO: use parameters for this
self.headerDict = other.headerDict
self.debug = other.debug
self.debugMacro = other.debugMacro
self.includeDirs = other.includeDirs[:]
self.expandHeaders = other.expandHeaders
self.inMacroDefine = other.inMacroDefine
self.stopOnHeader = other.stopOnHeader
def init (self):
self.firstNonBlank = ''
self.token = ''
self.tokens = []
self.defines = {}
self.visibilityStack = []
def getTokens (self):
return self.tokens
def getDefines (self):
return self.defines
def nextPos (self, i):
while True:
i += 1
try:
c = self.chars[i]
except IndexError:
raise EOF
if ord(c) in [0x0D]:
continue
break
return i
def prevPos (self, i):
while True:
i -= 1
try:
c = self.chars[i]
except IndexError:
raise BOF
if ord(c) in [0x0D]:
continue
break
return i
def isCodeVisible (self):
if len(self.visibilityStack) == 0:
return True
for item in self.visibilityStack:
if item != SrcLexer.VISIBLE:
return False
return True
def tokenize (self):
self.init()
i = 0
while True:
c = self.chars[i]
if self.firstNonBlank == '' and not c in [' ', "\n", "\t"]:
# Store the first non-blank in a line.
self.firstNonBlank = c
elif c == "\n":
self.firstNonBlank = ''
if c == '#':
i = self.pound(i)
elif c == '/':
i = self.slash(i)
elif c == "\n":
i = self.lineBreak(i)
elif c == '"':
i = self.doubleQuote(i)
elif c in [' ', "\t"]:
i = self.blank(i)
elif c in ";()[]{}<>,=+-*":
# Any outstanding single-character token.
i = self.anyToken(i, c)
elif self.isCodeVisible():
self.token += c
try:
i = self.nextPos(i)
except EOF:
break
if len(self.token):
self.tokens.append(self.token)
if not self.parentLexer and self.debug:
progress ("-"*68 + "\n")
progress ("All defines found in this translation unit:\n")
keys = sorted(self.defines.keys())
for key in keys:
progress ("@ %s\n"%key)
def dumpTokens (self, toError=False):
dumpTokens(self.tokens, toError)
def maybeAddToken (self):
if len(self.token) > 0:
self.tokens.append(self.token)
self.token = ''
#--------------------------------------------------------------------
# character handlers
def blank (self, i):
if not self.isCodeVisible():
return i
self.maybeAddToken()
return i
def pound (self, i):
if self.inMacroDefine:
return i
if not self.firstNonBlank == '#':
return i
self.maybeAddToken()
# We are in preprocessing mode.
# Get the macro command name '#<command> .....'
command, define, buf = '', '', ''
firstNonBlank = False
while True:
try:
i = self.nextPos(i)
c = self.chars[i]
if c == '\\' and self.chars[self.nextPos(i)] == "\n":
i = self.nextPos(i)
continue
except EOF:
break
if c == "\n":
if len(buf) > 0 and len(command) == 0:
command = buf
i = self.prevPos(i)
break
elif c in [' ', "\t"]:
if not firstNonBlank:
# Ignore any leading blanks after the '#'.
continue
if len(command) == 0:
command = buf
buf = ''
else:
buf += ' '
elif c == '(':
if len(buf) > 0 and len(command) == 0:
command = buf
buf += c
else:
if not firstNonBlank:
firstNonBlank = True
buf += c
if command == 'define':
self.handleMacroDefine(buf)
elif command == 'include':
self.handleMacroInclude(buf)
elif command == 'ifdef':
defineName = buf.strip()
if defineName in self.defines:
self.visibilityStack.append(SrcLexer.VISIBLE)
else:
self.visibilityStack.append(SrcLexer.INVISIBLE_PRE)
elif command == 'ifndef':
defineName = buf.strip()
if defineName in self.defines:
self.visibilityStack.append(SrcLexer.INVISIBLE_PRE)
else:
self.visibilityStack.append(SrcLexer.VISIBLE)
elif command == 'if':
if self.evalCodeVisibility(buf):
self.visibilityStack.append(SrcLexer.VISIBLE)
else:
self.visibilityStack.append(SrcLexer.INVISIBLE_PRE)
elif command == 'elif':
if len(self.visibilityStack) == 0:
raise ParseError ('')
if self.visibilityStack[-1] == SrcLexer.VISIBLE:
self.visibilityStack[-1] = SrcLexer.INVISIBLE_POST
elif self.visibilityStack[-1] == SrcLexer.INVISIBLE_PRE:
# Evaluate only if the current visibility is false.
if self.evalCodeVisibility(buf):
self.visibilityStack[-1] = SrcLexer.VISIBLE
elif command == 'else':
if len(self.visibilityStack) == 0:
raise ParseError ('')
if self.visibilityStack[-1] == SrcLexer.VISIBLE:
self.visibilityStack[-1] = SrcLexer.INVISIBLE_POST
if self.visibilityStack[-1] == SrcLexer.INVISIBLE_PRE:
self.visibilityStack[-1] = SrcLexer.VISIBLE
elif command == 'endif':
if len(self.visibilityStack) == 0:
raise ParseError ('')
self.visibilityStack.pop()
elif command == 'undef':
pass
elif command in ['error', 'pragma']:
pass
else:
print("'%s' '%s'"%(command, buf))
print(self.filepath)
sys.exit(0)
return i
def evalCodeVisibility (self, buf):
try:
return eval(buf)
except:
return True
def handleMacroDefine (self, buf):
mparser = macroparser.MacroParser(buf)
mparser.debug = self.debugMacro
mparser.parse()
macro = mparser.getMacro()
if macro:
self.defines[macro.name] = macro
def handleMacroInclude (self, buf):
# Strip excess string if any.
pos = buf.find(' ')
if pos >= 0:
buf = buf[:pos]
headerSub = removeHeaderQuotes(buf)
if not self.expandHeaders:
# We don't want to expand headers. Bail out.
if self.debug:
progress ("%s ignored\n"%headerSub)
return
defines = {}
headerPath = None
for includeDir in self.includeDirs:
hpath = includeDir + '/' + headerSub
if os.path.isfile(hpath) and hpath != self.filepath:
headerPath = hpath
break
if not headerPath:
error("included header file " + headerSub + " not found\n", self.stopOnHeader)
return
if self.debug:
progress ("%s found\n"%headerPath)
if headerPath in self.headerDict:
if self.debug:
progress ("%s already included\n"%headerPath)
return
if headerPath in SrcLexer.headerCache:
if self.debug:
progress ("%s in cache\n"%headerPath)
for key in list(SrcLexer.headerCache[headerPath].defines.keys()):
self.defines[key] = SrcLexer.headerCache[headerPath].defines[key]
return
chars = open(headerPath, 'r').read()
mclexer = SrcLexer(chars, headerPath)
mclexer.copyProperties(self)
mclexer.parentLexer = self
mclexer.tokenize()
hdrData = HeaderData()
hdrData.tokens = mclexer.getTokens()
headerDefines = mclexer.getDefines()
for key in list(headerDefines.keys()):
defines[key] = headerDefines[key]
hdrData.defines[key] = headerDefines[key]
self.headerDict[headerPath] = True
SrcLexer.headerCache[headerPath] = hdrData
# Update the list of headers that have already been expaneded.
for key in list(mclexer.headerDict.keys()):
self.headerDict[key] = True
if self.debug:
progress ("defines found in header %s:\n"%headerSub)
for key in list(defines.keys()):
progress (" '%s'\n"%key)
for key in list(defines.keys()):
self.defines[key] = defines[key]
def slash (self, i):
if not self.isCodeVisible():
return i
if i < self.bufsize - 1 and self.chars[i+1] == '/':
# Parse line comment.
line = ''
i += 2
while i < self.bufsize:
c = self.chars[i]
if ord(c) in [0x0A, 0x0D]:
return i - 1
line += c
i += 1
self.token = ''
elif i < self.bufsize - 1 and self.chars[i+1] == '*':
comment = ''
i += 2
while i < self.bufsize:
c = self.chars[i]
if c == '/' and self.chars[i-1] == '*':
return i
comment += c
i += 1
else:
return self.anyToken(i, '/')
return i
def lineBreak (self, i):
if not self.isCodeVisible():
return i
self.maybeAddToken()
return i
def doubleQuote (self, i):
if not self.isCodeVisible():
return i
literal = ''
i += 1
while i < self.bufsize:
c = self.chars[i]
if c == '"':
self.tokens.append('"'+literal+'"')
break
literal += c
i += 1
return i
def anyToken (self, i, token):
if not self.isCodeVisible():
return i
self.maybeAddToken()
self.token = token
self.maybeAddToken()
return i