def fix_non_derive_terminal(gramm:Grammar,return_derivations = False,left_derivation = True):
    """
    Remove from gramm the non terminals A that dont satisfy:\n
    A->*w  where w in {G.T}*
    return grammar 
    return grammar,derivation
    """
    gramm = gramm.copy()
    
    derivation = { x:[Production(x,Sentence(x)),] for x in gramm.terminals }
    derivation[gramm.Epsilon] = [Production(gramm.Epsilon,Sentence(gramm.Epsilon)),]
    derive_something = set(gramm.terminals)
    productions = set(gramm.Productions)
    
    change = -1
    while change != len(derive_something):
        change = len(derive_something)
        to_remove = []
        for x in productions:
            if not any([y for y in x.Right if not y in derive_something]): # if y ->* w with w in T*
                derive_something.add(x.Left)
                update_derivation(x,derivation,left_derivation)
                to_remove.append(x)
        for x in to_remove: productions.remove(x)
    
    remove_unnecessary_symbols(gramm,[x for x in gramm.nonTerminals if x not in derive_something])
    
    if return_derivations:
        return gramm,derivation
    return gramm
def fix_left_recursion(grammar:Grammar, errors):
    '''
    Fix immediate left recursion of grammar\n
    return a fixed copy of grammar
    '''
    new_grammar = grammar.copy()
    new_grammar.Productions = []
    
    for n_ter in grammar.nonTerminals:
        for prod in n_ter.productions:
            if not prod.Right.IsEpsilon and prod.Right[0] == prod.Left:
                fix_non_terminal_left_recursion(n_ter,new_grammar, errors)
                break
        else:
            new_grammar.Productions.extend(n_ter.productions)
    
    return new_grammar
def fix_unreachable_symbols(gramm:Grammar):
    gramm = gramm.copy()
    
    pending = [gramm.startSymbol]
    reachable = set(pending)
    while pending:
        symb = pending.pop()
        for prod in symb.productions:
            for r in prod.Right:
                if not r in reachable:
                    reachable.add(r)
                    if isinstance(r,NonTerminal):
                        pending.append(r)
    
    remove_unnecessary_symbols(gramm,[x for x in gramm.nonTerminals + gramm.terminals if x not in reachable])
    
    return gramm  
def fix_common_prefix(grammar:Grammar):
    """
    returns a copy of grammar without common prefixes
    """
    G = grammar.copy()
    G.Productions = []
    
    for non_terminal in grammar.nonTerminals:
        trie = Trie()
        epsilon = False
        for x in non_terminal.productions:
            if not x.Right.IsEpsilon:
                trie.add(x.Right)
            else:
                epsilon = True
        non_terminal.productions = []
        if epsilon:
            G.Add_Production(Production(x.Left,G.Epsilon))
        for node in trie.top.sons:
            execute_node(trie.top.sons[node],non_terminal,[],G,0)
    return G
def fix_unit_productions(gramm:Grammar):
    """
    returns an equivalent grammar without productions of the form:\n
    A -> B
    """
    gramm = gramm.copy()
    
    unit_productions = {x for x in gramm.Productions if len(x.Right) == 1 and x.Right[0].IsNonTerminal}

    new_productions = set()
    
    for x in gramm.Productions:
        if not x in unit_productions:
            new_productions.add(x)
    
    pending = get_unit_tuples(unit_productions)
    
    while pending:
        l,r = pending.pop()
        for prod in r.productions:
            if not prod in unit_productions:
                new_productions.add(Production(l,prod.Right))
        
    return change_grammar_from_productions(gramm,new_productions)