import os, re
import ConstExpr

continue_re = re.compile(r'\\\s*$')
define_re = re.compile(r'(?P<name>[a-zA-Z_][\w_-]*)\s*(?P<tokens>.*)')
undef_re = re.compile(r'(?P<name>[a-zA-Z_][\w_-]*)')
include_re = re.compile(r'("(?P<file1>[^"]*)")|(<(?P<file2>[^>]*)>)')
if_sub_re = re.compile(r'defined\s*\(\s*(?P<name>[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<directive>[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

