"""Algorithms which are related to US-American linguist N. Chomsky."""importdataclassesimportfunctoolsimportoperatorimporttypingimporttreelibfrommutwoimportcore_utilities__all__=("NonTerminal","Terminal","ContextFreeGrammarRule","ContextFreeGrammar")
[docs]classNonTerminal(object):"""Can be used as a Mixin to define context-free grammar."""
[docs]classTerminal(object):"""Can be used as a Mixin to define context-free grammar."""
[docs]@dataclasses.dataclassclassContextFreeGrammarRule(object):"""Describe a context_free_grammar_rule for a :class:`ContextFreeGrammar`"""left_side:NonTerminalright_side:tuple[typing.Union[NonTerminal,Terminal],...]
[docs]classContextFreeGrammar(object):"""Describe a context-free grammar and resolve non-terminals :param context_free_grammar_rule_sequence: A sequence of :class:`ContextFreeGrammarRule` objects. It is allowed to provide multiple context_free_grammar_rules with the same :attribute:`left_side`. :type context_free_grammar_rule_sequence: typing.Sequence[ContextFreeGrammarRule] This is a very reduced implementation of a context-free grammar which only provides the most basic functions. It is not made for the purpose of parsing text but rather as a technique to generate algorithmic data (for the sake of art creation). Therefore it is all about the resolution of start objects to variants of this start. """def__init__(self,context_free_grammar_rule_sequence:typing.Sequence[ContextFreeGrammarRule],):non_terminal_list=[]forcontext_free_grammar_ruleincontext_free_grammar_rule_sequence:non_terminal_list.append(context_free_grammar_rule.left_side)forterminal_or_non_terminalincontext_free_grammar_rule.right_side:ifisinstance(terminal_or_non_terminal,NonTerminal):non_terminal_list.append(terminal_or_non_terminal)self._non_terminal_tuple=tuple(core_utilities.uniqify_sequence(non_terminal_list))self._terminal_tuple=core_utilities.uniqify_sequence(tuple(itemforiteminfunctools.reduce(operator.add,tuple(context_free_grammar_rule.right_sideforcontext_free_grammar_ruleincontext_free_grammar_rule_sequence),)ifisinstance(item,Terminal)))divided_context_free_grammar_rule_list=[[]for_inself._non_terminal_tuple]forcontext_free_grammar_ruleincontext_free_grammar_rule_sequence:index=self._non_terminal_tuple.index(# type: ignorecontext_free_grammar_rule.left_side)divided_context_free_grammar_rule_list[index].append(context_free_grammar_rule)self._divided_context_free_grammar_rule_tuple=tuple(tuple(context_free_grammar_rule_list)forcontext_free_grammar_rule_listindivided_context_free_grammar_rule_list)self._context_free_grammar_rule_tuple=tuple(context_free_grammar_rule_sequence)def_data_to_tag(self,data:tuple[typing.Union[NonTerminal,Terminal],...])->str:returnstr(data)def_add_node(self,tree:treelib.Tree,data:tuple[typing.Union[NonTerminal,Terminal],...],parent:typing.Optional[treelib.Node]=None,):tree.create_node(self._data_to_tag(data),data=data,parent=parent)def_resolve_content(self,content:tuple[typing.Union[NonTerminal,Terminal],...])->tuple[tuple[typing.Union[NonTerminal,Terminal],...],...]:new_data_list=[]fornth_element,elementinenumerate(content):ifisinstance(element,NonTerminal):context_free_grammar_rule_tuple=(self.get_context_free_grammar_rule_tuple(element))forcontext_free_grammar_ruleincontext_free_grammar_rule_tuple:data=(content[:nth_element]+context_free_grammar_rule.right_side+content[nth_element+1:])new_data_list.append(data)returntuple(new_data_list)@propertydefnon_terminal_tuple(self)->tuple[NonTerminal,...]:returnself._non_terminal_tuple# type: ignore@propertydefterminal_tuple(self)->tuple[Terminal,...]:returnself._terminal_tuple# type: ignore@propertydefcontext_free_grammar_rule_tuple(self)->tuple[ContextFreeGrammarRule,...]:"""Get all defined rules"""returnself._context_free_grammar_rule_tuple
[docs]defget_context_free_grammar_rule_tuple(self,non_terminal:NonTerminal)->tuple[ContextFreeGrammarRule,...]:"""Find all defined context_free_grammar_rules for the provided :class:`NonTerminal`. :param non_terminal: The left side element of the :class:`ContextFreeGrammarRule`. :type non_terminal: NonTerminal """index=self._non_terminal_tuple.index(non_terminal)# type: ignorereturnself._divided_context_free_grammar_rule_tuple[index]
[docs]defresolve_one_layer(self,tree:treelib.Tree)->bool:"""Resolve all leaves of the tree. :param tree: The tree from which all leaves should be resolved. :type tree: treelib.Tree :return: `True` if any leaf has been resolved and `False` if no resolution has happened (e.g. if there are only :class:`Terminal` left). """new_node=Falseforleafintree.leaves():data_tuple=self._resolve_content(leaf.data)fordataindata_tuple:self._add_node(tree,data,leaf)new_node=Truereturnnew_node
[docs]defresolve(self,start:NonTerminal,limit:typing.Optional[int]=None)->treelib.Tree:"""Resolve until only :class:`Terminal` are left or the limit is reached. :param start: The start value. :type start: NonTerminal :param limit: The maximum node levels until the function returns a tree. If it is set to `None` it will only stop once all nodes are :class:`Terminal`. :type limit: typing.Optional[int] """defis_limit_reached()->bool:iflimitisNone:returnFalseelse:returnlimit<=countertree=treelib.Tree()self._add_node(tree,(start,))is_not_resolved=Truecounter=0whileis_not_resolvedandnotis_limit_reached():is_not_resolved=self.resolve_one_layer(tree)counter+=1returntree