import os, re import ConstExpr continue_re = re.compile(r'\\\s*$') define_re = re.compile(r'(?P[a-zA-Z_][\w_-]*)\s*(?P.*)') undef_re = re.compile(r'(?P[a-zA-Z_][\w_-]*)') include_re = re.compile(r'("(?P[^"]*)")|(<(?P[^>]*)>)') if_sub_re = re.compile(r'defined\s*\(\s*(?P[a-zA-Z_][\w_-]*)\s*\)') def Process(filename, prefix='#', defines=None): import cStringIO stream = cStringIO.StringIO() processor = Processor(prefix, defines or {}, stream) try: processor.process(filename) errors = 0 except: errors = 1 if processor.message_stack: messages = '\n'.join(processor.message_stack) if errors: raise SystemExit('%s\n%s: fatal error' % (messages, filename)) print messages return stream.getvalue() class IfStack: def __init__(self, type, succeeded): self.type = type self.succeeded = succeeded def __repr__(self): return "IfStack('%s', %s)" % (self.type, self.succeeded) class Processor: def __init__(self, prefix, defines, stream): self.prefix = prefix self.defines = defines self.stream = stream # Allow user-defined prefixes for preprocessor commands self.cmd_re = re.compile(r'\s*%s\s*(?P[a-z]+)\s*' % prefix) # For #if and #elif constant expressions self.expr_parser = ConstExpr.new() self.expr_parser.defines = self.defines # First entry maps to global scope self.if_stack = [IfStack('__main__', 1)] # For pretty display of errors in included files self.message_stack = [] def format_message(self, msg): if self.lineno: header = '%s:%d' % (self.filename, self.lineno) else: header = self.filename return '%s: %s' % (header, msg) def warning(self, msg): warning = self.format_message('warning: %s' % msg) self.message_stack.append(warning) return def error(self, msg): error = self.format_message(msg) self.message_stack.append(error) raise Exception(error) def readline(self): line = self.infile.readline() if not line: return self.lineno = self.lineno + 1 continued = continue_re.search(line) if continued: line = line[:continued.start()] + self.readline().strip() return line def process(self, filename): self.lineno = 0 self.base = os.path.dirname(filename) self.filename = filename try: self.infile = open(filename) except IOError, error: self.error(error.strerror) while 1: line = self.readline() if not line: # EOF is indicated by the empty string break command = self.cmd_re.match(line) if not command: if self.if_stack[-1].succeeded: self.stream.write(line) continue directive = command.group('directive') func = getattr(self, 'do_%s' % directive, None) if not func: self.error('invalid directive: %s' % directive) text = line[command.end():] func(text) return def do_define(self, text): match = define_re.match(text) if not match: self.error('#define without identifier') name = match.group('name') if self.defines.has_key(name): self.warning("'%s' redefined" % name) value = match.group('tokens') try: value = long(eval(value)) except: value = 0L self.defines[name] = value return def do_undef(self, text): match = undef_re.match(text) if not match: self.error('#undef without identifier') name = match.group('name') if self.defines.has_key(name): del self.defines[name] return def do_include(self, text): match = include_re.match(text) if not match: self.error('malformed #include: %s' % tokens) processor = Processor(self.prefix, self.defines, self.stream) include = match.group('file1') or match.group('file2') filename = os.path.join(self.base, include) try: processor.process(filename) error = None except Exception, error: pass if processor.message_stack: message = self.format_message('in included file: %s' % include) self.message_stack.append(message) self.message_stack.extend(processor.message_stack) if error: raise error return def do_if(self, text): value = self.eval_if_expr(text) self.conditional_skip(value, 'if') return def eval_if_expr(self, expr): try: # force 1/0 for true/false value = not not self.expr_parser.parse(expr) except Exception, error: print error self.error('malformed constant expression: %s' % expr) return value def conditional_skip(self, success, type): self.if_stack.append(IfStack(type, success)) if not success: self.skip_if_group() # the input stream is now positioned at the text which should # be written next (after all if-block evaluation) return def skip_if_group(self): """skip to #endif, #else, #elif. Adjust line numbers, etc.""" while 1: line = self.readline() if not line: # EOF is indicated by the empty string break command = self.cmd_re.match(line) if command: directive = command.group('directive') text = line[command.end():] if directive in ['if', 'ifdef', 'ifndef']: # just dummy it up, we're skipping it anyway self.conditional_skip(0, 'if') elif directive in ['elif', 'else', 'endif']: func = getattr(self, 'do_%s' % directive) func(text) break return def do_ifdef(self, text): match = undef_re.match(text) if not match: self.error('#ifdef without identifier') success = self.defines.has_key(match.group('name')) self.conditional_skip(success, 'if') return def do_ifndef(self, text): match = undef_re.match(text) if not match: self.error('#ifndef without identifier') success = not self.defines.has_key(match.group('name')) self.conditional_skip(success, 'if') return def do_elif(self, text): if len(self.if_stack) == 1: self.error('#elif not within a conditional') elif self.if_stack[-1].type == 'else': self.error('#elif after #else') success = self.eval_if_expr(text) self.if_stack[-1].type = 'elif' if self.if_stack[-1].succeeded or not success: self.skip_if_group() else: self.if_stack[-1].succeeded = success return def do_else(self, text): if len(self.if_stack) == 1: self.error('#else not within a conditional') elif self.if_stack[-1].type == 'else': self.error('#else after #else') elif text: self.warning('text following #else violates ANSI standard') self.if_stack[-1].type = 'else' if self.if_stack[-1].succeeded: self.skip_if_group() else: self.if_stack[-1].succeeded = 1 return def do_endif(self, text): if len(self.if_stack) == 1: self.error('unbalanced #endif') elif text: self.warning('text following #endif violates ANSI standard') del self.if_stack[-1] return def do_line(self, text): self.warning('#line directive not supported') return def do_pragma(self, text): # silently ignore #pragma directives # we might want to use them at some point return def do_error(self, text): self.error(text) return