import os, re, string, sys, errno
import IfExpr

continue_re = re.compile(r'\\\s*$')
identifier_re = re.compile(r'[a-zA-Z_][\w_]*')
directive_re = re.compile(r'\s*#\s*(?P<directive>[a-z]+)\s*')

# guaranteed to not have preceding whitespace
define_re = re.compile(r'(?P<name>[a-zA-Z_][\w_]*)')
undef_re = re.compile(r'(?P<name>[a-zA-Z_][\w_-]*)')
include_re = re.compile(r'("(?P<local>[^"]*)")|(<(?P<system>[^>]*)>)')
if_sub_re = re.compile(r'defined\s*\(\s*(?P<name>[a-zA-Z_][\w_-]*)\s*\)')
line_re = re.compile(r'(?P<line>\d+)\s*("(?P<filename>[^"]*)")?')

class _IfState:
    def __init__(self, type, succeeded):
        self.type = type
        self.succeeded = succeeded

    def __repr__(self):
        return "_IfState('%s', %s)" % (self.type, self.succeeded)

class Processor:
    def __init__(self, includes, defines, output=sys.stdout, error=sys.stderr):
        self.includes = includes
        self.defines = defines
        self.macros = {}
        self.output_stream = output
        self.error_stream = error

        # For #if and #elif constant expressions
        self.expr_parser = IfExpr.new()
        self.expr_parser.defines = self.defines
        
        # First entry maps to global scope
        self.if_stack = [_IfState('__main__', 1)]

        # For pretty display of errors in included files
        self.file_stack = []
        self.has_error = 0

    def message(self, msg):
        if len(self.file_stack):
            header = 'In file included from '
            indent = ' '*len(header)
            self.error_stream.write(header + '%s:%d' % self.file_stack[0])
            for (file, line) in self.file_stack[1:]:
                self.error_stream.write(',\n%s%s:%d' % (indent, file, line))
            self.error_stream.write(':\n')
        self.error_stream.write('%s:%d: %s' % (self.filename, self.lineno, msg))
        return

    def warning(self, msg):
        return self.message('warning: %s' % msg)

    def error(self, msg):
        self.has_error = 1
        return self.message(msg)

    def run(self, input):
        self.infile = input
        self.filename = getattr(input, 'name', '<string>')
        self.lineno = 0
        while 1:
            line = self.readline()
            if not line:
                # EOF is indicated by the empty string
                break
            command = directive_re.match(line)
            if not command:
                if self.if_stack[-1].succeeded:
                    self.output_stream.write(line)
                continue

            directive = command.group('directive')
            func = getattr(self, 'do_%s' % directive, None)
            if not func:
                self.warning('unsupported directive #%s' % directive)
            else:
                text = line[command.end():]
                func(text)
        return self.has_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()] + string.strip(self.readline())
        return line
        
    def do_define(self, text):
        match = define_re.match(text)
        if not match:
            self.error('#define without identifier')
            return
        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')
            return
        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)
            return

        processor = Processor(self.includes, self.defines,
                              self.output_stream, self.error_stream)
        processor.file_stack = [(self.filename, self.lineno)] + self.file_stack

        include = match.group('local') or match.group('system')
        dir = os.path.dirname(self.filename)
        if match.group('local'):
            includes = [dir] + self.includes
        else:
            includes = self.includes + [dir]

        for path in includes:
            filename = os.path.join(path, include)
            if os.path.isfile(filename):
                break
        else:
            self.error('%s: %s' % (include, os.strerror(errno.ENOENT)))
            return

        try:
            file = open(filename)
        except IOError, error:
            self.error('%s: %s' % (include, error.stderror))
            return
        
        self.has_error = processor.run(file)
        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
            return not not self.expr_parser.parse(expr)
        except SyntaxError:
            self.error('malformed constant expression: %s' % expr)

    def conditional_skip(self, success, type):
        self.if_stack.append(_IfState(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 = directive_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')
            return
        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')
            return
        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')
            return
        elif self.if_stack[-1].type == 'else':
            self.error('#elif after #else')
            return

        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')
            return
        elif self.if_stack[-1].type == 'else':
            self.error('#else after #else')
            return
        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')
            return
        elif text:
            self.warning('text following #endif violates ANSI standard')

        del self.if_stack[-1]
        return

    def do_line(self, text):
        match = line_re.match(text)
        if match:
            self.lineno = int(match.group('line'))
            self.filename = match.group('filename') or self.filename
        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

