# -*- coding: utf-8 -*-
from parser import functions as f
from parser.grammar import Grammar, InvalidGrammar
from parser.rule import Rule, InvalidProduction
from tests import test_data

import unittest


class TestGrammarEquality(unittest.TestCase):
    """Test Grammar equality."""

    def setUp(self):
        self.a = Grammar(start='X')
        self.b = Grammar(start='X')
        self.c = Grammar(start='X')

        self.a.add_rule(Rule('X', ('hello', 'Y')))
        self.a.add_rule(Rule('Y', ('world Z',)))
        self.a.add_rule(Rule('Z', ('?',)))
        self.a.add_rule(Rule('Z', ('!',)))

        # B will have productions in different order
        self.b.add_rule(Rule('Y', ('world Z',)))
        self.b.add_rule(Rule('Z', ('!',)))
        self.b.add_rule(Rule('Z', ('?',)))
        self.b.add_rule(Rule('X', ('hello', 'Y')))

        self.c.add_rule(Rule('X', ('bye', 'Y')))
        self.c.add_rule(Rule('Y', ('cruel world',)))

    def test_equal(self):
        self.assertEqual(self.a, self.b)
        self.assertEqual(self.b, self.a)

    def test_unequal(self):
        self.assertNotEqual(self.a, self.c)
        self.assertNotEqual(self.b, self.c)


class TestParseBNF(unittest.TestCase):
    """Basic test cases to parse BNF."""

    def test_simple(self):
        a = Grammar(start='E')
        a.add_rule(Rule('E', ('E', '+', 'T')))
        a.add_rule(Rule('E', ('T',)))
        a.add_rule(Rule('T', ('T', '*', 'F')))
        a.add_rule(Rule('T', ('F',)))
        a.add_rule(Rule('F', ('(', 'E', ')')))
        a.add_rule(Rule('F', ('id',)))
        text = str(a)
        g = f.parse_bnf(text)
        self.assertEqual(a, g)

    def test_invalid_grammar(self):
        text = "INVALID"
        with self.assertRaises(InvalidGrammar):
            f.parse_bnf(text)

    def test_invalid_production(self):
        text = "E -> E + T | E"  # Production is the same as nonterminal
        with self.assertRaises(InvalidProduction):
            f.parse_bnf(text)

    def test_cases(self):
        for case in test_data.examples:
            g = f.parse_bnf(case)
            text = str(g)
            self.assertEqual(g, f.parse_bnf(text))


class TestRemoveLeftRecursion(unittest.TestCase):
    def test_immediate_recursion(self):
        p = Rule('E', ('E', '+' 'T'))
        self.assertTrue(p.is_left_recursive())

    def test_book_example(self):
        solved = f.parse_bnf(test_data.solved_indirect_recursion_book_example)
        unsolved = f.parse_bnf(test_data.unsolved_indirect_recursion_book_example)
        g = f.remove_left_recursion(unsolved)
        self.assertEqual(solved, g)

    def test_simple_book_example(self):
        solved = f.parse_bnf(test_data.solved_left_recursion)
        unsolved = f.parse_bnf(test_data.unsolved_left_recursion)
        g = f.remove_left_recursion(unsolved)
        self.assertEqual(solved, g)

    def test_cases(self):
        for case in test_data.examples:
            g = f.parse_bnf(case)
            g = f.remove_left_recursion(g)
            for p in g.iter_productions():
                self.assertFalse(p.is_left_recursive(), msg='{} is left-recursive'.format(p))


class TestRemoveLeftFactoring(unittest.TestCase):
    def test_check_left_factor(self):
        solved = f.parse_bnf(test_data.solved_left_factoring)
        unsolved = f.parse_bnf(test_data.unsolved_left_factoring)
        self.assertTrue(f.check_left_factors(unsolved))
        self.assertFalse(f.check_left_factors(solved))

    def test_book_example(self):
        """
        S -> i E t S | i E t S e S | a
        E -> b
        """
        solved = f.parse_bnf(test_data.solved_left_factoring)
        unsolved = f.parse_bnf(test_data.unsolved_left_factoring)
        g = f.remove_left_factoring(unsolved)
        self.assertEqual(solved, g)

    def test_cases(self):
        for case in test_data.examples:
            g = f.parse_bnf(case)
            g = f.remove_left_factoring(g)
            self.assertFalse(f.check_left_factors(g), msg='{} has left factors'.format(g))


class TestFirst(unittest.TestCase):
    def test_book_example(self):
        g = f.parse_bnf(test_data.book_example)
        answers = {
            'E': {'(', 'id'},
            'E\'': {'+', 'ε'},
            'T': {'(', 'id'},
            'T\'': {'*', 'ε'},
            'F': {'(', 'id'}
        }

        for x, first in answers.items():
            self.assertEqual(first, set(g.first(x)))

    def test_cases(self):
        try:
            for case in test_data.examples:
                g = f.parse_bnf(case)
                h = f.remove_left_recursion(g)
                i = f.remove_left_factoring(h)
                for x in i.nonterminals:
                    i.first(x)
        except Exception as e:
            self.fail(str(e))


class TestFollow(unittest.TestCase):
    def test_book_example(self):
        g = f.parse_bnf(test_data.book_example)
        answers = {
            'E': {'$', ')'},
            'E\'': {'$', ')'},
            'T': {'$', ')', '+'},
            'T\'': {'$', ')', '+'},
            'F': {'$', ')', '*', '+'}
        }

        for x, first in answers.items():
            self.assertEqual(first, set(g.follow(x)))

    def test_cases(self):
        try:
            for case in test_data.examples:
                g = f.remove_left_factoring(f.remove_left_recursion(f.parse_bnf(case)))
                for x in g.nonterminals:
                    g.follow(x)
        except Exception as e:
            self.fail(str(e))


class TestParsingTable(unittest.TestCase):
    def test_book_example(self):
        g = f.parse_bnf(test_data.unsolved_left_recursion)
        # Trust me, this is the answer
        answer = {
            ("T'", '+'): Rule("T'", ('ε',)), ('F', 'id'): Rule('F', ('id',)),
            ("E'", '+'): Rule("E'", ('+', 'T', "E'")), ('E', '('): Rule('E', ('T', "E'")),
            ('T', '('): Rule('T', ('F', "T'")), ("E'", '$'): Rule("E'", ('ε',)),
            ("T'", '*'): Rule("T'", ('*', 'F', "T'")), ("T'", ')'): Rule("T'", ('ε',)),
            ("T'", '$'): Rule("T'", ('ε',)), ("E'", ')'): Rule("E'", ('ε',)), ('T', 'id'): Rule('T', ('F', "T'")),
            ('E', 'id'): Rule('E', ('T', "E'")), ('F', '('): Rule('F', ('(', 'E', ')'))
        }

        table, amb = g.parsing_table(is_clean=False)

        self.assertFalse(amb)
        self.assertEqual(table, answer)

    def test_ambiguous(self):
        for case in test_data.ambiguous:
            g = f.parse_bnf(case)
            table, amb = g.parsing_table(is_clean=False)

            self.assertTrue(amb)

    def test_cases(self):
        try:
            for case in test_data.examples:
                g = f.parse_bnf(case)
                g.parsing_table(is_clean=False)
        except Exception as e:
            self.fail(str(e))


class TestComplete(unittest.TestCase):
    def setUp(self):
        self.g = f.parse_bnf(test_data.exam_exercise)

    def test_parsing(self):
        h = Grammar(start='P')
        h.add_rule(Rule('P', ('D',)))
        h.add_rule(Rule('D', ('T', ':', 'id', ';', 'D')))
        h.add_rule(Rule('D', ('ε',)))
        h.add_rule(Rule('T', ('real',)))
        h.add_rule(Rule('T', ('int',)))
        self.assertEqual(self.g, h)

    def test_terminals(self):
        correct = {':', 'id', ';', 'ε', 'real', 'int'}
        terminals = set(self.g.terminals)
        self.assertEqual(terminals, correct)

    def test_nonterminals(self):
        correct = {'P', 'D', 'T'}
        nonterminals = set(self.g.nonterminals)
        self.assertEqual(nonterminals, correct)

    def test_first(self):
        correct = {
            'P': {'real', 'int', 'ε'},
            'D': {'real', 'int', 'ε'},
            'T': {'real', 'int'}
        }

        for x in self.g.nonterminals:
            self.assertEqual(set(self.g.first(x)), correct[x])

    def test_follow(self):
        correct = {
            'P': {'$'},
            'D': {'$'},
            'T': {':'}
        }

        for x in self.g.nonterminals:
            self.assertEqual(set(self.g.follow(x)), correct[x])

    def test_parsing_table(self):
        correct = {
            ('P', 'real'): Rule('P', ('D',)),
            ('P', 'int'): Rule('P', ('D',)),
            ('P', '$'): Rule('P', ('D',)),
            ('D', 'int'): Rule('D', ('T', ':', 'id', ';', 'D')),
            ('D', 'real'): Rule('D', ('T', ':', 'id', ';', 'D')),
            ('D', '$'): Rule('D', ('ε',)),
            ('T', 'int'): Rule('T', ('int',)),
            ('T', 'real'): Rule('T', ('real',))
        }

        table, amb = self.g.parsing_table()
        self.assertFalse(amb)
        self.assertEqual(table, correct)


if __name__ == '__main__':
    unittest.main()

# -*- coding: utf-8 -*-
from parser import functions as f
from parser.grammar import Grammar, InvalidGrammar
from parser.rule import Rule, InvalidProduction
from tests import test_data

import unittest


class TestGrammarEquality(unittest.TestCase):
    """Test Grammar equality."""

    def setUp(self):
        self.a = Grammar(start='X')
        self.b = Grammar(start='X')
        self.c = Grammar(start='X')

        self.a.add_rule(Rule('X', ('hello', 'Y')))
        self.a.add_rule(Rule('Y', ('world Z',)))
        self.a.add_rule(Rule('Z', ('?',)))
        self.a.add_rule(Rule('Z', ('!',)))

        # B will have productions in different order
        self.b.add_rule(Rule('Y', ('world Z',)))
        self.b.add_rule(Rule('Z', ('!',)))
        self.b.add_rule(Rule('Z', ('?',)))
        self.b.add_rule(Rule('X', ('hello', 'Y')))

        self.c.add_rule(Rule('X', ('bye', 'Y')))
        self.c.add_rule(Rule('Y', ('cruel world',)))

    def test_equal(self):
        self.assertEqual(self.a, self.b)
        self.assertEqual(self.b, self.a)

    def test_unequal(self):
        self.assertNotEqual(self.a, self.c)
        self.assertNotEqual(self.b, self.c)


class TestParseBNF(unittest.TestCase):
    """Basic test cases to parse BNF."""

    def test_simple(self):
        a = Grammar(start='E')
        a.add_rule(Rule('E', ('E', '+', 'T')))
        a.add_rule(Rule('E', ('T',)))
        a.add_rule(Rule('T', ('T', '*', 'F')))
        a.add_rule(Rule('T', ('F',)))
        a.add_rule(Rule('F', ('(', 'E', ')')))
        a.add_rule(Rule('F', ('id',)))
        text = str(a)
        g = f.parse_bnf(text)
        self.assertEqual(a, g)

    def test_invalid_grammar(self):
        text = "INVALID"
        with self.assertRaises(InvalidGrammar):
            f.parse_bnf(text)

    def test_invalid_production(self):
        text = "E -> E + T | E"  # Production is the same as nonterminal
        with self.assertRaises(InvalidProduction):
            f.parse_bnf(text)

    def test_cases(self):
        for case in test_data.examples:
            g = f.parse_bnf(case)
            text = str(g)
            self.assertEqual(g, f.parse_bnf(text))


class TestRemoveLeftRecursion(unittest.TestCase):
    def test_immediate_recursion(self):
        p = Rule('E', ('E', '+' 'T'))
        self.assertTrue(p.is_left_recursive())

    def test_book_example(self):
        solved = f.parse_bnf(test_data.solved_indirect_recursion_book_example)
        unsolved = f.parse_bnf(test_data.unsolved_indirect_recursion_book_example)
        g = f.remove_left_recursion(unsolved)
        self.assertEqual(solved, g)

    def test_simple_book_example(self):
        solved = f.parse_bnf(test_data.solved_left_recursion)
        unsolved = f.parse_bnf(test_data.unsolved_left_recursion)
        g = f.remove_left_recursion(unsolved)
        self.assertEqual(solved, g)

    def test_cases(self):
        for case in test_data.examples:
            g = f.parse_bnf(case)
            g = f.remove_left_recursion(g)
            for p in g.iter_productions():
                self.assertFalse(p.is_left_recursive(), msg='{} is left-recursive'.format(p))


class TestRemoveLeftFactoring(unittest.TestCase):
    def test_check_left_factor(self):
        solved = f.parse_bnf(test_data.solved_left_factoring)
        unsolved = f.parse_bnf(test_data.unsolved_left_factoring)
        self.assertTrue(f.check_left_factors(unsolved))
        self.assertFalse(f.check_left_factors(solved))

    def test_book_example(self):
        """
        S -> i E t S | i E t S e S | a
        E -> b
        """
        solved = f.parse_bnf(test_data.solved_left_factoring)
        unsolved = f.parse_bnf(test_data.unsolved_left_factoring)
        g = f.remove_left_factoring(unsolved)
        self.assertEqual(solved, g)

    def test_cases(self):
        for case in test_data.examples:
            g = f.parse_bnf(case)
            g = f.remove_left_factoring(g)
            self.assertFalse(f.check_left_factors(g), msg='{} has left factors'.format(g))


class TestFirst(unittest.TestCase):
    def test_book_example(self):
        g = f.parse_bnf(test_data.book_example)
        answers = {
            'E': {'(', 'id'},
            'E\'': {'+', 'ε'},
            'T': {'(', 'id'},
            'T\'': {'*', 'ε'},
            'F': {'(', 'id'}
        }

        for x, first in answers.items():
            self.assertEqual(first, set(g.first(x)))

    def test_cases(self):
        try:
            for case in test_data.examples:
                g = f.parse_bnf(case)
                h = f.remove_left_recursion(g)
                i = f.remove_left_factoring(h)
                for x in i.nonterminals:
                    i.first(x)
        except Exception as e:
            self.fail(str(e))


class TestFollow(unittest.TestCase):
    def test_book_example(self):
        g = f.parse_bnf(test_data.book_example)
        answers = {
            'E': {'$', ')'},
            'E\'': {'$', ')'},
            'T': {'$', ')', '+'},
            'T\'': {'$', ')', '+'},
            'F': {'$', ')', '*', '+'}
        }

        for x, first in answers.items():
            self.assertEqual(first, set(g.follow(x)))

    def test_cases(self):
        try:
            for case in test_data.examples:
                g = f.remove_left_factoring(f.remove_left_recursion(f.parse_bnf(case)))
                for x in g.nonterminals:
                    g.follow(x)
        except Exception as e:
            self.fail(str(e))


class TestParsingTable(unittest.TestCase):
    def test_book_example(self):
        g = f.parse_bnf(test_data.unsolved_left_recursion)
        # Trust me, this is the answer
        answer = {
            ("T'", '+'): Rule("T'", ('ε',)), ('F', 'id'): Rule('F', ('id',)),
            ("E'", '+'): Rule("E'", ('+', 'T', "E'")), ('E', '('): Rule('E', ('T', "E'")),
            ('T', '('): Rule('T', ('F', "T'")), ("E'", '$'): Rule("E'", ('ε',)),
            ("T'", '*'): Rule("T'", ('*', 'F', "T'")), ("T'", ')'): Rule("T'", ('ε',)),
            ("T'", '$'): Rule("T'", ('ε',)), ("E'", ')'): Rule("E'", ('ε',)), ('T', 'id'): Rule('T', ('F', "T'")),
            ('E', 'id'): Rule('E', ('T', "E'")), ('F', '('): Rule('F', ('(', 'E', ')'))
        }

        table, amb = g.parsing_table(is_clean=False)

        self.assertFalse(amb)
        self.assertEqual(table, answer)

    def test_ambiguous(self):
        for case in test_data.ambiguous:
            g = f.parse_bnf(case)
            table, amb = g.parsing_table(is_clean=False)

            self.assertTrue(amb)

    def test_cases(self):
        try:
            for case in test_data.examples:
                g = f.parse_bnf(case)
                g.parsing_table(is_clean=False)
        except Exception as e:
            self.fail(str(e))


class TestComplete(unittest.TestCase):
    def setUp(self):
        self.g = f.parse_bnf(test_data.exam_exercise)

    def test_parsing(self):
        h = Grammar(start='P')
        h.add_rule(Rule('P', ('D',)))
        h.add_rule(Rule('D', ('T', ':', 'id', ';', 'D')))
        h.add_rule(Rule('D', ('ε',)))
        h.add_rule(Rule('T', ('real',)))
        h.add_rule(Rule('T', ('int',)))
        self.assertEqual(self.g, h)

    def test_terminals(self):
        correct = {':', 'id', ';', 'ε', 'real', 'int'}
        terminals = set(self.g.terminals)
        self.assertEqual(terminals, correct)

    def test_nonterminals(self):
        correct = {'P', 'D', 'T'}
        nonterminals = set(self.g.nonterminals)
        self.assertEqual(nonterminals, correct)

    def test_first(self):
        correct = {
            'P': {'real', 'int', 'ε'},
            'D': {'real', 'int', 'ε'},
            'T': {'real', 'int'}
        }

        for x in self.g.nonterminals:
            self.assertEqual(set(self.g.first(x)), correct[x])

    def test_follow(self):
        correct = {
            'P': {'$'},
            'D': {'$'},
            'T': {':'}
        }

        for x in self.g.nonterminals:
            self.assertEqual(set(self.g.follow(x)), correct[x])

    def test_parsing_table(self):
        correct = {
            ('P', 'real'): Rule('P', ('D',)),
            ('P', 'int'): Rule('P', ('D',)),
            ('P', '$'): Rule('P', ('D',)),
            ('D', 'int'): Rule('D', ('T', ':', 'id', ';', 'D')),
            ('D', 'real'): Rule('D', ('T', ':', 'id', ';', 'D')),
            ('D', '$'): Rule('D', ('ε',)),
            ('T', 'int'): Rule('T', ('int',)),
            ('T', 'real'): Rule('T', ('real',))
        }

        table, amb = self.g.parsing_table()
        self.assertFalse(amb)
        self.assertEqual(table, correct)


if __name__ == '__main__':
    unittest.main()

# -*- coding: utf-8 -*- Test Grammar equality. # B will have productions in different order Basic test cases to parse BNF. # Production is the same as nonterminal S -> i E t S | i E t S e S | a         E -> b # Trust me, this is the answer