| Viewing file:  cpp.py (32.5 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
# -----------------------------------------------------------------------------# 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)
 
 |