#!/usr/bin/env python

import pprint
import re
import os, sys
import unittest

sys.path[0:0] = ['.', '..']

from pycparser import c_parser
from pycparser.c_ast import *
from pycparser.c_parser import CParser, Coord, ParseError

_c_parser = c_parser.CParser(
                lex_optimize=False,
                yacc_debug=True,
                yacc_optimize=False,
                yacctab='yacctab')


def expand_decl(decl):
    """ Converts the declaration into a nested list.
    """
    typ = type(decl)

    if typ == TypeDecl:
        return ['TypeDecl', expand_decl(decl.type)]
    elif typ == IdentifierType:
        return ['IdentifierType', decl.names]
    elif typ == ID:
        return ['ID', decl.name]
    elif typ in [Struct, Union]:
        decls = [expand_decl(d) for d in decl.decls or []]
        return [typ.__name__, decl.name, decls]
    else:
        nested = expand_decl(decl.type)

        if typ == Decl:
            if decl.quals:
                return ['Decl', decl.quals, decl.name, nested]
            else:
                return ['Decl', decl.name, nested]
        elif typ == Typename: # for function parameters
            if decl.quals:
                return ['Typename', decl.quals, nested]
            else:
                return ['Typename', nested]
        elif typ == ArrayDecl:
            dimval = decl.dim.value if decl.dim else ''
            return ['ArrayDecl', dimval, decl.dim_quals, nested]
        elif typ == PtrDecl:
            if decl.quals:
                return ['PtrDecl', decl.quals, nested]
            else:
                return ['PtrDecl', nested
]
        elif typ == Typedef:
            return ['Typedef', decl.name, nested]
        elif typ == FuncDecl:
            if decl.args:
                params 
= [expand_decl(param) for param in decl.args.params]
            else:
                params = []
            return ['FuncDecl', params, nested]


def expand_init(init):
    """ Converts an initialization into a nested list
    """
    typ = type(init)

    if typ == NamedInitializer:
        des = [expand_init(dp) for dp in init.name]
        return (des, expand_init(init.expr))
    elif typ in (InitList, ExprList):
        return [expand_init(expr) for expr in init.exprs]
    elif typ == Constant:
        return ['Constant', init.type, init.value]
    elif typ == ID:
        return ['ID', init.name]


class TestCParser_base(unittest.TestCase):
    def parse(self, txt, filename=''):
        return self.cparser.parse(txt, filename)

    def setUp(self):
        self.cparser = _c_parser


class TestCParser_fundamentals(TestCParser_base):
    def get_decl(self, txt, index=0):
        """ Given a source and an index returns the expanded
            declaration at that index.

            FileAST holds a list of 'external declarations'.
            index is the offset of the desired declaration in that
            list.
        """
        t = self.parse(txt).ext[index]
        return expand_decl(t)

    def get_decl_init(self, txt, index=0):
        """ Returns the expanded initializer of the declaration
            at index.
        """
        t = self.parse(txt).ext[index]
        return expand_init(t.init)

    def test_FileAST(self):
        t = self.parse('int a; char c;')
        self.assertTrue(isinstance(t, FileAST))
        self.assertEqual(len(t.ext), 2)

        # empty file
        t2 = self.parse('')
        self.assertTrue(isinstance(t2, FileAST))
        self.assertEqual(len(t2.ext), 0)

    def test_empty_toplevel_decl(self):
        code = 'int foo;;'
        t = self.parse(code)
        self.assertTrue(isinstance(t, FileAST))
        self.assertEqual(len(t.ext), 1)
        self.assertEqual(self.get_decl(code),
            ['Decl', 'foo',
                ['TypeDecl', ['IdentifierType', ['int']]]])

    def assert_coord(self, node, line, file=None):
        self.assertEqual(node.coord.line, line)
        if file:
            self.assertEqual(node.coord.file, file)

    def test_coords(self):
        """ Tests the "coordinates" of parsed elements - file
            name and line numbers, with modification insterted by
            #line directives.
        """
        self.assert_coord(self.parse('int a;').ext[0], 1)

        t1 = """
        int a;
        int b;\n\n
        int c;
        """
        f1 = self.parse(t1, filename='test.c')
        self.assert_coord(f1.ext[0], 2, 'test.c')
        self.assert_coord(f1.ext[1], 3, 'test.c')
        self.assert_coord(f1.ext[2], 6, 'test.c')

        t1_1 = '''
        int main() {
            k = p;
            printf("%d", b);
            return 0;
        }'''
        f1_1 = self.parse(t1_1, filename='test.c')
        self.assert_coord(f1_1.ext[0].body.block_items[0], 3, 'test.c')
        self.assert_coord(f1_1.ext[0].body.block_items[1], 4, 'test.c')

        t1_2 = '''
        int main () {
            int p = (int) k;
        }'''
        f1_2 = self.parse(t1_2, filename='test.c')
        # make sure that the Cast has a coord (issue 23)
        self.assert_coord(f1_2.ext[0].body.block_items[0].init, 3, 'test.c')

        t2 = """
        #line 99
        int c;
        """
        self.assert_coord(self.parse(t2).ext[0], 99)

        t3 = """
        int dsf;
        char p;
        #line 3000 "in.h"
        char d;
        """
        f3 = self.parse(t3, filename='test.c')
        self.assert_coord(f3.ext[0], 2, 'test.c')
        self.assert_coord(f3.ext[1], 3, 'test.c')
        self.assert_coord(f3.ext[2], 3000, 'in.h')

        t4 = """
        #line 20 "restore.h"
        int maydler(char);

        #line 30 "includes/daween.ph"
        long j, k;

        #line 50000
        char* ro;
        """
        f4 = self.parse(t4, filename='myb.c')
        self.assert_coord(f4.ext[0], 20, 'restore.h')
        self.assert_coord(f4.ext[1], 30, 'includes/daween.ph')
        self.assert_coord(f4.ext[2], 30, 'includes/daween.ph')
        self.assert_coord(f4.ext[3], 50000, 'includes/daween.ph')

        t5 = """
        int
        #line 99
        c;
        """
        self.assert_coord(self.parse(t5).ext[0], 99)

        # coord for ellipsis
        t6 = """
        int foo(int j,
                ...) {
        }"""
        f6 = self.parse(t6, filename='z.c')
        self.assert_coord(self.parse(t6).ext[0].decl.type.args.params[1], 3)

    def test_forloop_coord(self):
        t = '''\
        void foo() {
            for(int z=0; z<4;
                z++){}
        }
        '''
        s = self.parse(t, filename='f.c')
        forloop = s.ext[0].body.block_items[0]
        self.assert_coord(forloop.init, 2, 'f.c')
        self.assert_coord(forloop.cond, 2, 'f.c')
        self.assert_coord(forloop.next, 3, 'f.c')

    def test_simple_decls(self):
        self.assertEqual(self.get_decl('int a;'),
            ['Decl', 'a', ['TypeDecl', ['IdentifierType', ['int']]]])

        self.assertEqual(self.get_decl('unsigned int a;'),
            ['Decl', 'a', ['TypeDecl', ['IdentifierType', ['unsigned', 'int']]]])

        self.assertEqual(self.get_decl('_Bool a;'),
            ['Decl', 'a', ['TypeDecl', ['IdentifierType', ['_Bool']]]])

        self.assertEqual(self.get_decl('float _Complex fcc;'),
            ['Decl', 'fcc', ['TypeDecl', ['IdentifierType', ['float', '_Complex']]]])

        self.assertEqual(self.get_decl('char* string;'),
            ['Decl', 'string',
                ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]])

        self.assertEqual(self.get_decl('long ar[15];'),
            ['Decl', 'ar',
                ['ArrayDecl', '15', [],
                    ['TypeDecl', ['IdentifierType', ['long']]]]])

        self.assertEqual(self.get_decl('long long ar[15];'),
            ['Decl', 'ar',
                ['ArrayDecl', '15', [],
                    ['TypeDecl', ['IdentifierType', ['long', 'long']]]]])

        self.assertEqual(self.get_decl('unsigned ar[];'),
            ['Decl', 'ar',
                ['ArrayDecl', '', [],
                    ['TypeDecl', ['IdentifierType', ['unsigned']]]]])

        self.assertEqual(self.get_decl('int strlen(char* s);'),
            ['Decl', 'strlen',
                ['FuncD