You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
906 lines
32 KiB
906 lines
32 KiB
# -----------------------------------------------------------------------------
|
|
# cpp.py
|
|
#
|
|
# Author: David Beazley (http://www.dabeaz.com)
|
|
# Copyright (C) 2017
|
|
# All rights reserved
|
|
#
|
|
# This module implements an ANSI-C style lexical preprocessor for PLY.
|
|
# -----------------------------------------------------------------------------
|
|
import sys
|
|
|
|
# Some Python 3 compatibility shims
|
|
if sys.version_info.major < 3:
|
|
STRING_TYPES = (str, unicode)
|
|
else:
|
|
STRING_TYPES = str
|
|
xrange = range
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Default preprocessor lexer definitions. These tokens are enough to get
|
|
# a basic preprocessor working. Other modules may import these if they want
|
|
# -----------------------------------------------------------------------------
|
|
|
|
tokens = (
|
|
'CPP_ID','CPP_INTEGER', 'CPP_FLOAT', 'CPP_STRING', 'CPP_CHAR', 'CPP_WS', 'CPP_COMMENT1', 'CPP_COMMENT2', 'CPP_POUND','CPP_DPOUND'
|
|
)
|
|
|
|
literals = "+-*/%|&~^<>=!?()[]{}.,;:\\\'\""
|
|
|
|
# Whitespace
|
|
def t_CPP_WS(t):
|
|
r'\s+'
|
|
t.lexer.lineno += t.value.count("\n")
|
|
return t
|
|
|
|
t_CPP_POUND = r'\#'
|
|
t_CPP_DPOUND = r'\#\#'
|
|
|
|
# Identifier
|
|
t_CPP_ID = r'[A-Za-z_][\w_]*'
|
|
|
|
# Integer literal
|
|
def CPP_INTEGER(t):
|
|
r'(((((0x)|(0X))[0-9a-fA-F]+)|(\d+))([uU][lL]|[lL][uU]|[uU]|[lL])?)'
|
|
return t
|
|
|
|
t_CPP_INTEGER = CPP_INTEGER
|
|
|
|
# Floating literal
|
|
t_CPP_FLOAT = r'((\d+)(\.\d+)(e(\+|-)?(\d+))? | (\d+)e(\+|-)?(\d+))([lL]|[fF])?'
|
|
|
|
# String literal
|
|
def t_CPP_STRING(t):
|
|
r'\"([^\\\n]|(\\(.|\n)))*?\"'
|
|
t.lexer.lineno += t.value.count("\n")
|
|
return t
|
|
|
|
# Character constant 'c' or L'c'
|
|
def t_CPP_CHAR(t):
|
|
r'(L)?\'([^\\\n]|(\\(.|\n)))*?\''
|
|
t.lexer.lineno += t.value.count("\n")
|
|
return t
|
|
|
|
# Comment
|
|
def t_CPP_COMMENT1(t):
|
|
r'(/\*(.|\n)*?\*/)'
|
|
ncr = t.value.count("\n")
|
|
t.lexer.lineno += ncr
|
|
# replace with one space or a number of '\n'
|
|
t.type = 'CPP_WS'; t.value = '\n' * ncr if ncr else ' '
|
|
return t
|
|
|
|
# Line comment
|
|
def t_CPP_COMMENT2(t):
|
|
r'(//.*?(\n|$))'
|
|
# replace with '/n'
|
|
t.type = 'CPP_WS'; t.value = '\n'
|
|
return t
|
|
|
|
def t_error(t):
|
|
t.type = t.value[0]
|
|
t.value = t.value[0]
|
|
t.lexer.skip(1)
|
|
return t
|
|
|
|
import re
|
|
import copy
|
|
import time
|
|
import os.path
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# trigraph()
|
|
#
|
|
# Given an input string, this function replaces all trigraph sequences.
|
|
# The following mapping is used:
|
|
#
|
|
# ??= #
|
|
# ??/ \
|
|
# ??' ^
|
|
# ??( [
|
|
# ??) ]
|
|
# ??! |
|
|
# ??< {
|
|
# ??> }
|
|
# ??- ~
|
|
# -----------------------------------------------------------------------------
|
|
|
|
_trigraph_pat = re.compile(r'''\?\?[=/\'\(\)\!<>\-]''')
|
|
_trigraph_rep = {
|
|
'=':'#',
|
|
'/':'\\',
|
|
"'":'^',
|
|
'(':'[',
|
|
')':']',
|
|
'!':'|',
|
|
'<':'{',
|
|
'>':'}',
|
|
'-':'~'
|
|
}
|
|
|
|
def trigraph(input):
|
|
return _trigraph_pat.sub(lambda g: _trigraph_rep[g.group()[-1]],input)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Macro object
|
|
#
|
|
# This object holds information about preprocessor macros
|
|
#
|
|
# .name - Macro name (string)
|
|
# .value - Macro value (a list of tokens)
|
|
# .arglist - List of argument names
|
|
# .variadic - Boolean indicating whether or not variadic macro
|
|
# .vararg - Name of the variadic parameter
|
|
#
|
|
# When a macro is created, the macro replacement token sequence is
|
|
# pre-scanned and used to create patch lists that are later used
|
|
# during macro expansion
|
|
# ------------------------------------------------------------------
|
|
|
|
class Macro(object):
|
|
def __init__(self,name,value,arglist=None,variadic=False):
|
|
self.name = name
|
|
self.value = value
|
|
self.arglist = arglist
|
|
self.variadic = variadic
|
|
if variadic:
|
|
self.vararg = arglist[-1]
|
|
self.source = None
|
|
|
|
# ------------------------------------------------------------------
|
|
# Preprocessor object
|
|
#
|
|
# Object representing a preprocessor. Contains macro definitions,
|
|
# include directories, and other information
|
|
# ------------------------------------------------------------------
|
|
|
|
class Preprocessor(object):
|
|
def __init__(self,lexer=None):
|
|
if lexer is None:
|
|
lexer = lex.lexer
|
|
self.lexer = lexer
|
|
self.macros = { }
|
|
self.path = []
|
|
self.temp_path = []
|
|
|
|
# Probe the lexer for selected tokens
|
|
self.lexprobe()
|
|
|
|
tm = time.localtime()
|
|
self.define("__DATE__ \"%s\"" % time.strftime("%b %d %Y",tm))
|
|
self.define("__TIME__ \"%s\"" % time.strftime("%H:%M:%S",tm))
|
|
self.parser = None
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# tokenize()
|
|
#
|
|
# Utility function. Given a string of text, tokenize into a list of tokens
|
|
# -----------------------------------------------------------------------------
|
|
|
|
def tokenize(self,text):
|
|
tokens = []
|
|
self.lexer.input(text)
|
|
while True:
|
|
tok = self.lexer.token()
|
|
if not tok: break
|
|
tokens.append(tok)
|
|
return tokens
|
|
|
|
# ---------------------------------------------------------------------
|
|
# error()
|
|
#
|
|
# Report a preprocessor error/warning of some kind
|
|
# ----------------------------------------------------------------------
|
|
|
|
def error(self,file,line,msg):
|
|
print("%s:%d %s" % (file,line,msg))
|
|
|
|
# ----------------------------------------------------------------------
|
|
# lexprobe()
|
|
#
|
|
# This method probes the preprocessor lexer object to discover
|
|
# the token types of symbols that are important to the preprocessor.
|
|
# If this works right, the preprocessor will simply "work"
|
|
# with any suitable lexer regardless of how tokens have been named.
|
|
# ----------------------------------------------------------------------
|
|
|
|
def lexprobe(self):
|
|
|
|
# Determine the token type for identifiers
|
|
self.lexer.input("identifier")
|
|
tok = self.lexer.token()
|
|
if not tok or tok.value != "identifier":
|
|
print("Couldn't determine identifier type")
|
|
else:
|
|
self.t_ID = tok.type
|
|
|
|
# Determine the token type for integers
|
|
self.lexer.input("12345")
|
|
tok = self.lexer.token()
|
|
if not tok or int(tok.value) != 12345:
|
|
print("Couldn't determine integer type")
|
|
else:
|
|
self.t_INTEGER = tok.type
|
|
self.t_INTEGER_TYPE = type(tok.value)
|
|
|
|
# Determine the token type for strings enclosed in double quotes
|
|
self.lexer.input("\"filename\"")
|
|
tok = self.lexer.token()
|
|
if not tok or tok.value != "\"filename\"":
|
|
print("Couldn't determine string type")
|
|
else:
|
|
self.t_STRING = tok.type
|
|
|
|
# Determine the token type for whitespace--if any
|
|
self.lexer.input(" ")
|
|
tok = self.lexer.token()
|
|
if not tok or tok.value != " ":
|
|
self.t_SPACE = None
|
|
else:
|
|
self.t_SPACE = tok.type
|
|
|
|
# Determine the token type for newlines
|
|
self.lexer.input("\n")
|
|
tok = self.lexer.token()
|
|
if not tok or tok.value != "\n":
|
|
self.t_NEWLINE = None
|
|
print("Couldn't determine token for newlines")
|
|
else:
|
|
self.t_NEWLINE = tok.type
|
|
|
|
self.t_WS = (self.t_SPACE, self.t_NEWLINE)
|
|
|
|
# Check for other characters used by the preprocessor
|
|
chars = [ '<','>','#','##','\\','(',')',',','.']
|
|
for c in chars:
|
|
self.lexer.input(c)
|
|
tok = self.lexer.token()
|
|
if not tok or tok.value != c:
|
|
print("Unable to lex '%s' required for preprocessor" % c)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# add_path()
|
|
#
|
|
# Adds a search path to the preprocessor.
|
|
# ----------------------------------------------------------------------
|
|
|
|
def add_path(self,path):
|
|
self.path.append(path)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# group_lines()
|
|
#
|
|
# Given an input string, this function splits it into lines. Trailing whitespace
|
|
# is removed. Any line ending with \ is grouped with the next line. This
|
|
# function forms the lowest level of the preprocessor---grouping into text into
|
|
# a line-by-line format.
|
|
# ----------------------------------------------------------------------
|
|
|
|
def group_lines(self,input):
|
|
lex = self.lexer.clone()
|
|
lines = [x.rstrip() for x in input.splitlines()]
|
|
for i in xrange(len(lines)):
|
|
j = i+1
|
|
while lines[i].endswith('\\') and (j < len(lines)):
|
|
lines[i] = lines[i][:-1]+lines[j]
|
|
lines[j] = ""
|
|
j += 1
|
|
|
|
input = "\n".join(lines)
|
|
lex.input(input)
|
|
lex.lineno = 1
|
|
|
|
current_line = []
|
|
while True:
|
|
tok = lex.token()
|
|
if not tok:
|
|
break
|
|
current_line.append(tok)
|
|
if tok.type in self.t_WS and '\n' in tok.value:
|
|
yield current_line
|
|
current_line = []
|
|
|
|
if current_line:
|
|
yield current_line
|
|
|
|
# ----------------------------------------------------------------------
|
|
# tokenstrip()
|
|
#
|
|
# Remove leading/trailing whitespace tokens from a token list
|
|
# ----------------------------------------------------------------------
|
|
|
|
def tokenstrip(self,tokens):
|
|
i = 0
|
|
while i < len(tokens) and tokens[i].type in self.t_WS:
|
|
i += 1
|
|
del tokens[:i]
|
|
i = len(tokens)-1
|
|
while i >= 0 and tokens[i].type in self.t_WS:
|
|
i -= 1
|
|
del tokens[i+1:]
|
|
return tokens
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# collect_args()
|
|
#
|
|
# Collects comma separated arguments from a list of tokens. The arguments
|
|
# must be enclosed in parenthesis. Returns a tuple (tokencount,args,positions)
|
|
# where tokencount is the number of tokens consumed, args is a list of arguments,
|
|
# and positions is a list of integers containing the starting index of each
|
|
# argument. Each argument is represented by a list of tokens.
|
|
#
|
|
# When collecting arguments, leading and trailing whitespace is removed
|
|
# from each argument.
|
|
#
|
|
# This function properly handles nested parenthesis and commas---these do not
|
|
# define new arguments.
|
|
# ----------------------------------------------------------------------
|
|
|
|
def collect_args(self,tokenlist):
|
|
args = []
|
|
positions = []
|
|
current_arg = []
|
|
nesting = 1
|
|
tokenlen = len(tokenlist)
|
|
|
|
# Search for the opening '('.
|
|
i = 0
|
|
while (i < tokenlen) and (tokenlist[i].type in self.t_WS):
|
|
i += 1
|
|
|
|
if (i < tokenlen) and (tokenlist[i].value == '('):
|
|
positions.append(i+1)
|
|
else:
|
|
self.error(self.source,tokenlist[0].lineno,"Missing '(' in macro arguments")
|
|
return 0, [], []
|
|
|
|
i += 1
|
|
|
|
while i < tokenlen:
|
|
t = tokenlist[i]
|
|
if t.value == '(':
|
|
current_arg.append(t)
|
|
nesting += 1
|
|
elif t.value == ')':
|
|
nesting -= 1
|
|
if nesting == 0:
|
|
if current_arg:
|
|
args.append(self.tokenstrip(current_arg))
|
|
positions.append(i)
|
|
return i+1,args,positions
|
|
current_arg.append(t)
|
|
elif t.value == ',' and nesting == 1:
|
|
args.append(self.tokenstrip(current_arg))
|
|
positions.append(i+1)
|
|
current_arg = []
|
|
else:
|
|
current_arg.append(t)
|
|
i += 1
|
|
|
|
# Missing end argument
|
|
self.error(self.source,tokenlist[-1].lineno,"Missing ')' in macro arguments")
|
|
return 0, [],[]
|
|
|
|
# ----------------------------------------------------------------------
|
|
# macro_prescan()
|
|
#
|
|
# Examine the macro value (token sequence) and identify patch points
|
|
# This is used to speed up macro expansion later on---we'll know
|
|
# right away where to apply patches to the value to form the expansion
|
|
# ----------------------------------------------------------------------
|
|
|
|
def macro_prescan(self,macro):
|
|
macro.patch = [] # Standard macro arguments
|
|
macro.str_patch = [] # String conversion expansion
|
|
macro.var_comma_patch = [] # Variadic macro comma patch
|
|
i = 0
|
|
while i < len(macro.value):
|
|
if macro.value[i].type == self.t_ID and macro.value[i].value in macro.arglist:
|
|
argnum = macro.arglist.index(macro.value[i].value)
|
|
# Conversion of argument to a string
|
|
if i > 0 and macro.value[i-1].value == '#':
|
|
macro.value[i] = copy.copy(macro.value[i])
|
|
macro.value[i].type = self.t_STRING
|
|
del macro.value[i-1]
|
|
macro.str_patch.append((argnum,i-1))
|
|
continue
|
|
# Concatenation
|
|
elif (i > 0 and macro.value[i-1].value == '##'):
|
|
macro.patch.append(('c',argnum,i-1))
|
|
del macro.value[i-1]
|
|
continue
|
|
elif ((i+1) < len(macro.value) and macro.value[i+1].value == '##'):
|
|
macro.patch.append(('c',argnum,i))
|
|
i += 1
|
|
continue
|
|
# Standard expansion
|
|
else:
|
|
macro.patch.append(('e',argnum,i))
|
|
elif macro.value[i].value == '##':
|
|
if macro.variadic and (i > 0) and (macro.value[i-1].value == ',') and \
|
|
((i+1) < len(macro.value)) and (macro.value[i+1].type == self.t_ID) and \
|
|
(macro.value[i+1].value == macro.vararg):
|
|
macro.var_comma_patch.append(i-1)
|
|
i += 1
|
|
macro.patch.sort(key=lambda x: x[2],reverse=True)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# macro_expand_args()
|
|
#
|
|
# Given a Macro and list of arguments (each a token list), this method
|
|
# returns an expanded version of a macro. The return value is a token sequence
|
|
# representing the replacement macro tokens
|
|
# ----------------------------------------------------------------------
|
|
|
|
def macro_expand_args(self,macro,args):
|
|
# Make a copy of the macro token sequence
|
|
rep = [copy.copy(_x) for _x in macro.value]
|
|
|
|
# Make string expansion patches. These do not alter the length of the replacement sequence
|
|
|
|
str_expansion = {}
|
|
for argnum, i in macro.str_patch:
|
|
if argnum not in str_expansion:
|
|
str_expansion[argnum] = ('"%s"' % "".join([x.value for x in args[argnum]])).replace("\\","\\\\")
|
|
rep[i] = copy.copy(rep[i])
|
|
rep[i].value = str_expansion[argnum]
|
|
|
|
# Make the variadic macro comma patch. If the variadic macro argument is empty, we get rid
|
|
comma_patch = False
|
|
if macro.variadic and not args[-1]:
|
|
for i in macro.var_comma_patch:
|
|
rep[i] = None
|
|
comma_patch = True
|
|
|
|
# Make all other patches. The order of these matters. It is assumed that the patch list
|
|
# has been sorted in reverse order of patch location since replacements will cause the
|
|
# size of the replacement sequence to expand from the patch point.
|
|
|
|
expanded = { }
|
|
for ptype, argnum, i in macro.patch:
|
|
# Concatenation. Argument is left unexpanded
|
|
if ptype == 'c':
|
|
rep[i:i+1] = args[argnum]
|
|
# Normal expansion. Argument is macro expanded first
|
|
elif ptype == 'e':
|
|
if argnum not in expanded:
|
|
expanded[argnum] = self.expand_macros(args[argnum])
|
|
rep[i:i+1] = expanded[argnum]
|
|
|
|
# Get rid of removed comma if necessary
|
|
if comma_patch:
|
|
rep = [_i for _i in rep if _i]
|
|
|
|
return rep
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# expand_macros()
|
|
#
|
|
# Given a list of tokens, this function performs macro expansion.
|
|
# The expanded argument is a dictionary that contains macros already
|
|
# expanded. This is used to prevent infinite recursion.
|
|
# ----------------------------------------------------------------------
|
|
|
|
def expand_macros(self,tokens,expanded=None):
|
|
if expanded is None:
|
|
expanded = {}
|
|
i = 0
|
|
while i < len(tokens):
|
|
t = tokens[i]
|
|
if t.type == self.t_ID:
|
|
if t.value in self.macros and t.value not in expanded:
|
|
# Yes, we found a macro match
|
|
expanded[t.value] = True
|
|
|
|
m = self.macros[t.value]
|
|
if not m.arglist:
|
|
# A simple macro
|
|
ex = self.expand_macros([copy.copy(_x) for _x in m.value],expanded)
|
|
for e in ex:
|
|
e.lineno = t.lineno
|
|
tokens[i:i+1] = ex
|
|
i += len(ex)
|
|
else:
|
|
# A macro with arguments
|
|
j = i + 1
|
|
while j < len(tokens) and tokens[j].type in self.t_WS:
|
|
j += 1
|
|
if tokens[j].value == '(':
|
|
tokcount,args,positions = self.collect_args(tokens[j:])
|
|
if not m.variadic and len(args) != len(m.arglist):
|
|
self.error(self.source,t.lineno,"Macro %s requires %d arguments" % (t.value,len(m.arglist)))
|
|
i = j + tokcount
|
|
elif m.variadic and len(args) < len(m.arglist)-1:
|
|
if len(m.arglist) > 2:
|
|
self.error(self.source,t.lineno,"Macro %s must have at least %d arguments" % (t.value, len(m.arglist)-1))
|
|
else:
|
|
self.error(self.source,t.lineno,"Macro %s must have at least %d argument" % (t.value, len(m.arglist)-1))
|
|
i = j + tokcount
|
|
else:
|
|
if m.variadic:
|
|
if len(args) == len(m.arglist)-1:
|
|
args.append([])
|
|
else:
|
|
args[len(m.arglist)-1] = tokens[j+positions[len(m.arglist)-1]:j+tokcount-1]
|
|
del args[len(m.arglist):]
|
|
|
|
# Get macro replacement text
|
|
rep = self.macro_expand_args(m,args)
|
|
rep = self.expand_macros(rep,expanded)
|
|
for r in rep:
|
|
r.lineno = t.lineno
|
|
tokens[i:j+tokcount] = rep
|
|
i += len(rep)
|
|
del expanded[t.value]
|
|
continue
|
|
elif t.value == '__LINE__':
|
|
t.type = self.t_INTEGER
|
|
t.value = self.t_INTEGER_TYPE(t.lineno)
|
|
|
|
i += 1
|
|
return tokens
|
|
|
|
# ----------------------------------------------------------------------
|
|
# evalexpr()
|
|
#
|
|
# Evaluate an expression token sequence for the purposes of evaluating
|
|
# integral expressions.
|
|
# ----------------------------------------------------------------------
|
|
|
|
def evalexpr(self,tokens):
|
|
# tokens = tokenize(line)
|
|
# Search for defined macros
|
|
i = 0
|
|
while i < len(tokens):
|
|
if tokens[i].type == self.t_ID and tokens[i].value == 'defined':
|
|
j = i + 1
|
|
needparen = False
|
|
result = "0L"
|
|
while j < len(tokens):
|
|
if tokens[j].type in self.t_WS:
|
|
j += 1
|
|
continue
|
|
elif tokens[j].type == self.t_ID:
|
|
if tokens[j].value in self.macros:
|
|
result = "1L"
|
|
else:
|
|
result = "0L"
|
|
if not needparen: break
|
|
elif tokens[j].value == '(':
|
|
needparen = True
|
|
elif tokens[j].value == ')':
|
|
break
|
|
else:
|
|
self.error(self.source,tokens[i].lineno,"Malformed defined()")
|
|
j += 1
|
|
tokens[i].type = self.t_INTEGER
|
|
tokens[i].value = self.t_INTEGER_TYPE(result)
|
|
del tokens[i+1:j+1]
|
|
i += 1
|
|
tokens = self.expand_macros(tokens)
|
|
for i,t in enumerate(tokens):
|
|
if t.type == self.t_ID:
|
|
tokens[i] = copy.copy(t)
|
|
tokens[i].type = self.t_INTEGER
|
|
tokens[i].value = self.t_INTEGER_TYPE("0L")
|
|
elif t.type == self.t_INTEGER:
|
|
tokens[i] = copy.copy(t)
|
|
# Strip off any trailing suffixes
|
|
tokens[i].value = str(tokens[i].value)
|
|
while tokens[i].value[-1] not in "0123456789abcdefABCDEF":
|
|
tokens[i].value = tokens[i].value[:-1]
|
|
|
|
expr = "".join([str(x.value) for x in tokens])
|
|
expr = expr.replace("&&"," and ")
|
|
expr = expr.replace("||"," or ")
|
|
expr = expr.replace("!"," not ")
|
|
try:
|
|
result = eval(expr)
|
|
except Exception:
|
|
self.error(self.source,tokens[0].lineno,"Couldn't evaluate expression")
|
|
result = 0
|
|
return result
|
|
|
|
# ----------------------------------------------------------------------
|
|
# parsegen()
|
|
#
|
|
# Parse an input string/
|
|
# ----------------------------------------------------------------------
|
|
def parsegen(self,input,source=None):
|
|
|
|
# Replace trigraph sequences
|
|
t = trigraph(input)
|
|
lines = self.group_lines(t)
|
|
|
|
if not source:
|
|
source = ""
|
|
|
|
self.define("__FILE__ \"%s\"" % source)
|
|
|
|
self.source = source
|
|
chunk = []
|
|
enable = True
|
|
iftrigger = False
|
|
ifstack = []
|
|
|
|
for x in lines:
|
|
for i,tok in enumerate(x):
|
|
if tok.type not in self.t_WS: break
|
|
if tok.value == '#':
|
|
# Preprocessor directive
|
|
|
|
# insert necessary whitespace instead of eaten tokens
|
|
for tok in x:
|
|
if tok.type in self.t_WS and '\n' in tok.value:
|
|
chunk.append(tok)
|
|
|
|
dirtokens = self.tokenstrip(x[i+1:])
|
|
if dirtokens:
|
|
name = dirtokens[0].value
|
|
args = self.tokenstrip(dirtokens[1:])
|
|
else:
|
|
name = ""
|
|
args = []
|
|
|
|
if name == 'define':
|
|
if enable:
|
|
for tok in self.expand_macros(chunk):
|
|
yield tok
|
|
chunk = []
|
|
self.define(args)
|
|
elif name == 'include':
|
|
if enable:
|
|
for tok in self.expand_macros(chunk):
|
|
yield tok
|
|
chunk = []
|
|
oldfile = self.macros['__FILE__']
|
|
for tok in self.include(args):
|
|
yield tok
|
|
self.macros['__FILE__'] = oldfile
|
|
self.source = source
|
|
elif name == 'undef':
|
|
if enable:
|
|
for tok in self.expand_macros(chunk):
|
|
yield tok
|
|
chunk = []
|
|
self.undef(args)
|
|
elif name == 'ifdef':
|
|
ifstack.append((enable,iftrigger))
|
|
if enable:
|
|
if not args[0].value in self.macros:
|
|
enable = False
|
|
iftrigger = False
|
|
else:
|
|
iftrigger = True
|
|
elif name == 'ifndef':
|
|
ifstack.append((enable,iftrigger))
|
|
if enable:
|
|
if args[0].value in self.macros:
|
|
enable = False
|
|
iftrigger = False
|
|
else:
|
|
iftrigger = True
|
|
elif name == 'if':
|
|
ifstack.append((enable,iftrigger))
|
|
if enable:
|
|
result = self.evalexpr(args)
|
|
if not result:
|
|
enable = False
|
|
iftrigger = False
|
|
else:
|
|
iftrigger = True
|
|
elif name == 'elif':
|
|
if ifstack:
|
|
if ifstack[-1][0]: # We only pay attention if outer "if" allows this
|
|
if enable: # If already true, we flip enable False
|
|
enable = False
|
|
elif not iftrigger: # If False, but not triggered yet, we'll check expression
|
|
result = self.evalexpr(args)
|
|
if result:
|
|
enable = True
|
|
iftrigger = True
|
|
else:
|
|
self.error(self.source,dirtokens[0].lineno,"Misplaced #elif")
|
|
|
|
elif name == 'else':
|
|
if ifstack:
|
|
if ifstack[-1][0]:
|
|
if enable:
|
|
enable = False
|
|
elif not iftrigger:
|
|
enable = True
|
|
iftrigger = True
|
|
else:
|
|
self.error(self.source,dirtokens[0].lineno,"Misplaced #else")
|
|
|
|
elif name == 'endif':
|
|
if ifstack:
|
|
enable,iftrigger = ifstack.pop()
|
|
else:
|
|
self.error(self.source,dirtokens[0].lineno,"Misplaced #endif")
|
|
else:
|
|
# Unknown preprocessor directive
|
|
pass
|
|
|
|
else:
|
|
# Normal text
|
|
if enable:
|
|
chunk.extend(x)
|
|
|
|
for tok in self.expand_macros(chunk):
|
|
yield tok
|
|
chunk = []
|
|
|
|
# ----------------------------------------------------------------------
|
|
# include()
|
|
#
|
|
# Implementation of file-inclusion
|
|
# ----------------------------------------------------------------------
|
|
|
|
def include(self,tokens):
|
|
# Try to extract the filename and then process an include file
|
|
if not tokens:
|
|
return
|
|
if tokens:
|
|
if tokens[0].value != '<' and tokens[0].type != self.t_STRING:
|
|
tokens = self.expand_macros(tokens)
|
|
|
|
if tokens[0].value == '<':
|
|
# Include <...>
|
|
i = 1
|
|
while i < len(tokens):
|
|
if tokens[i].value == '>':
|
|
break
|
|
i += 1
|
|
else:
|
|
print("Malformed #include <...>")
|
|
return
|
|
filename = "".join([x.value for x in tokens[1:i]])
|
|
path = self.path + [""] + self.temp_path
|
|
elif tokens[0].type == self.t_STRING:
|
|
filename = tokens[0].value[1:-1]
|
|
path = self.temp_path + [""] + self.path
|
|
else:
|
|
print("Malformed #include statement")
|
|
return
|
|
for p in path:
|
|
iname = os.path.join(p,filename)
|
|
try:
|
|
data = open(iname,"r").read()
|
|
dname = os.path.dirname(iname)
|
|
if dname:
|
|
self.temp_path.insert(0,dname)
|
|
for tok in self.parsegen(data,filename):
|
|
yield tok
|
|
if dname:
|
|
del self.temp_path[0]
|
|
break
|
|
except IOError:
|
|
pass
|
|
else:
|
|
print("Couldn't find '%s'" % filename)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# define()
|
|
#
|
|
# Define a new macro
|
|
# ----------------------------------------------------------------------
|
|
|
|
def define(self,tokens):
|
|
if isinstance(tokens,STRING_TYPES):
|
|
tokens = self.tokenize(tokens)
|
|
|
|
linetok = tokens
|
|
try:
|
|
name = linetok[0]
|
|
if len(linetok) > 1:
|
|
mtype = linetok[1]
|
|
else:
|
|
mtype = None
|
|
if not mtype:
|
|
m = Macro(name.value,[])
|
|
self.macros[name.value] = m
|
|
elif mtype.type in self.t_WS:
|
|
# A normal macro
|
|
m = Macro(name.value,self.tokenstrip(linetok[2:]))
|
|
self.macros[name.value] = m
|
|
elif mtype.value == '(':
|
|
# A macro with arguments
|
|
tokcount, args, positions = self.collect_args(linetok[1:])
|
|
variadic = False
|
|
for a in args:
|
|
if variadic:
|
|
print("No more arguments may follow a variadic argument")
|
|
break
|
|
astr = "".join([str(_i.value) for _i in a])
|
|
if astr == "...":
|
|
variadic = True
|
|
a[0].type = self.t_ID
|
|
a[0].value = '__VA_ARGS__'
|
|
variadic = True
|
|
del a[1:]
|
|
continue
|
|
elif astr[-3:] == "..." and a[0].type == self.t_ID:
|
|
variadic = True
|
|
del a[1:]
|
|
# If, for some reason, "." is part of the identifier, strip off the name for the purposes
|
|
# of macro expansion
|
|
if a[0].value[-3:] == '...':
|
|
a[0].value = a[0].value[:-3]
|
|
continue
|
|
if len(a) > 1 or a[0].type != self.t_ID:
|
|
print("Invalid macro argument")
|
|
break
|
|
else:
|
|
mvalue = self.tokenstrip(linetok[1+tokcount:])
|
|
i = 0
|
|
while i < len(mvalue):
|
|
if i+1 < len(mvalue):
|
|
if mvalue[i].type in self.t_WS and mvalue[i+1].value == '##':
|
|
del mvalue[i]
|
|
continue
|
|
elif mvalue[i].value == '##' and mvalue[i+1].type in self.t_WS:
|
|
del mvalue[i+1]
|
|
i += 1
|
|
m = Macro(name.value,mvalue,[x[0].value for x in args],variadic)
|
|
self.macro_prescan(m)
|
|
self.macros[name.value] = m
|
|
else:
|
|
print("Bad macro definition")
|
|
except LookupError:
|
|
print("Bad macro definition")
|
|
|
|
# ----------------------------------------------------------------------
|
|
# undef()
|
|
#
|
|
# Undefine a macro
|
|
# ----------------------------------------------------------------------
|
|
|
|
def undef(self,tokens):
|
|
id = tokens[0].value
|
|
try:
|
|
del self.macros[id]
|
|
except LookupError:
|
|
pass
|
|
|
|
# ----------------------------------------------------------------------
|
|
# parse()
|
|
#
|
|
# Parse input text.
|
|
# ----------------------------------------------------------------------
|
|
def parse(self,input,source=None,ignore={}):
|
|
self.ignore = ignore
|
|
self.parser = self.parsegen(input,source)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# token()
|
|
#
|
|
# Method to return individual tokens
|
|
# ----------------------------------------------------------------------
|
|
def token(self):
|
|
try:
|
|
while True:
|
|
tok = next(self.parser)
|
|
if tok.type not in self.ignore: return tok
|
|
except StopIteration:
|
|
self.parser = None
|
|
return None
|
|
|
|
if __name__ == '__main__':
|
|
import ply.lex as lex
|
|
lexer = lex.lex()
|
|
|
|
# Run a preprocessor
|
|
import sys
|
|
f = open(sys.argv[1])
|
|
input = f.read()
|
|
|
|
p = Preprocessor(lexer)
|
|
p.parse(input,sys.argv[1])
|
|
while True:
|
|
tok = p.token()
|
|
if not tok: break
|
|
print(p.source, tok)
|