def __remove_epsilon_productions(grammar):
    S, d = grammar_to_graph(grammar)
    nonterminals = [t.name for t in grammar.nonterminals]

    nullable = {}
    nullable = __find_nullable_nonterminals(d, nullable, S, nonterminals)

    for key, value in d.items():
        new_value = [v for v in value]

        for sentence in value:
            if sentence == []:
                new_value.remove(sentence)

            for i in range(0, len(sentence)):
                if sentence[i] in nonterminals and nullable[sentence[i]]:
                    new_sentence = sentence[0:i] + sentence[i +
                                                            1:len(sentence)]

                    if not new_sentence in new_value:
                        new_value.append(new_sentence)

        d[key] = new_value

    if nullable[S]:
        d[S].append([])

    return graph_to_grammar(S, d)
def remove_left_recursion(grammar):
    new_grammar = __remove_epsilon_productions(grammar)
    new_grammar = remove_unit_prods(new_grammar)

    nonterminals = [t.name for t in new_grammar.nonterminals]

    S, d = grammar_to_graph(new_grammar)

    for i in range(0, len(nonterminals)):
        for j in range(0, i):
            for sentence in d[nonterminals[i]]:
                if sentence[0] == nonterminals[j]:
                    d[nonterminals[i]].remove(sentence)
                    remove_first = sentence[1:len(sentence)]

                    for sentence in d[nonterminals[j]]:
                        new_sentence = []
                        for item in sentence:
                            new_sentence.append(item)
                        for item in remove_first:
                            new_sentence.append(item)
                        d[nonterminals[i]].append(new_sentence)
        d = __remove_inmediate_left_recursion(d)

    return graph_to_grammar(S, d)
def remove_unit_prods(G: Grammar):
    S, d = grammar_to_graph(G)
    nonterminals = [t.name for t in G.nonterminals]
    new_d = {}

    u = __find_unitary_pairs(d, nonterminals)
    for pair in u:
        for sentence in d[pair[1]]:
            if not (len(sentence) == 1 and sentence[0] in nonterminals):
                try:
                    if not sentence in new_d[pair[0]]:
                        new_d[pair[0]].append(sentence)
                except KeyError:
                    new_d[pair[0]] = [sentence]

    return graph_to_grammar(S, new_d)
def remove_unreachable_prods(G: Grammar):
    S, d = grammar_to_graph(G)
    nonterminals = [t.name for t in G.nonterminals]

    mark = {}

    for p in d.keys():
        mark[p[0]] = False

    __overlook(d, mark, nonterminals, S)

    for t in nonterminals:
        if not mark[t]:
            _ = d.pop(t)

    return graph_to_grammar(S, d)
def remove_common_prefixes(grammar: Grammar):
    S, d = grammar_to_graph(grammar)
    nonterminals = [nt.name for nt in grammar.nonterminals]

    for A in nonterminals:
        try:
            _ = d[A]
        except KeyError:
            continue

        count = 1
        trie = Trie((A, d[A]))
        prefix_nodes = [n for n in trie.prefix_nodes]
        prefix_nodes.sort(key=lambda x: x.depth, reverse=True)

        for (n) in (
                prefix_nodes
        ):  # get the longest common prefix among the productions be the prefix α
            productions = trie.get_node_productions(
                n)  # get all the productions with that prefix
            n.children.clear()

            # A -> α ω1 | α ω2 | ... | α ωΝ
            # replace those productions with
            # A -> αA'
            # A' -> ω1 | ω2 | ... | ωΝ

            A_new = A + ("'" * count)
            count += 1
            d[A] = [productions[0][0:n.depth + 1] + [A_new]]
            n.productions = [d[A][-1]]

            for p in productions:
                if len(p) > n.depth + 1:
                    try:
                        d[A_new].append(p[n.depth + 1:])
                    except KeyError:
                        d[A_new] = [p[n.depth + 1:]]
                else:
                    try:
                        d[A_new].append([])
                    except KeyError:
                        d[A_new] = [[]]

    print(d)
    return graph_to_grammar(S, d)