diff --git a/README.md b/README.md index 67d4de4..88191fe 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,107 @@ -# minimalism -Implementation of A Formalization of Minimalist Syntax (Collins & Stabler, 2016) +# Parser minimalista +Este es un parser basado en la implementación de Alex Warstadt del formalismo descrito en ["A Formalization of Minimalist Syntax"](https://onlinelibrary.wiley.com/doi/abs/10.1111/synt.12117) por Chris Collins y Edward Stabler (2016). Su objetivo es determinar si las oraciones introducidas por el usuario son o no gramaticales, y devolver en caso positivo una estructura sintáctica. + +El parser es capaz de reconocer oraciones transitivas (*el perro comió el hueso/the dog ate the bone*), oraciones inergativas (*el perro corrió/the dog run*) y oraciones inacusativas (*el perro llegó*/*the dog arrived*) + +## Modo de uso +Para utilizar el programa es necesario ejecutar el archivo `main.py`, ubicado en la carpeta principal del repositorio. Hecho esto, el usuario verá un menú de cinco opciones: + +**1. Parser** *(Permite introducir la oración a parsear)*
+**2. Manual derivation** *(Permite generar la derivación manualmente, como en la implementación de Warstadt)*
+**3. Change grammar** *(Permite seleccionar una gramática distinta)*
+**4. Enable/disable stage-by-stage view** *(Cuando está activado, muestra el paso a paso de una derivación, incluyendo las reglas aplicadas)*
+**5. Quit** *(Finaliza el programa)* + +### La gramática +Por defecto, el Lexicon de la gramática se genera a partir del archivo `lexicon.xml` de la carpeta `data`. La elección de este archivo puede ser modificada mediante la opción 3 del menú principal.
+Las gramáticas están definidas en el formato XML, que organiza el léxico como un árbol de nodos. A cada ítem le corresponde un elemento con la etiqueta ``, dentro del cual se insertan nuevos elementos con una etiqueta distinta para cada rasgo: la etiqueta `` corresponde a los rasgos fonético, `` a los sintácticos, y `` a los semánticos (aunque estos últimos no son utilizados en la implementación actual). Por ejemplo, la entrada léxica correspondiente a un verbo como *corrió* sería la siguiente: +``` + + llegó + + V + + none + + + comió + + V + D + D/ + + none + +``` +Los rasgos sintácticos se subdividen en rasgos categoriales (`Cat_Feature`) y de selección (`Trigger_Feature`). Los primeros se insertan dentro de la etiqueta ``; y los segundos, dentro de ``.
+#### Merge interno +Los rasgos de selección pueden ir acompañados de una **barra al final** (`D/`). Este operador le indica a la gramática que el ítem léxico en cuestión se somete a merge interno junto a otro ítem de la categoría declarada (en este caso, *llegó* primero se combina mediante merge externo con un determinante, y luego se combina con este determinante mediante merge interno, lo que en la práctica supone que el D se "mueva" o más bien se copie en la posición de especificador del SV). Este operador hace que el merge interno sólo pueda efectuarse con ciertos ítems léxicos, y una vez que los demás rasgos de selección ya fueron cotejados. Fue necesario añadir esta restricción, que no aparece en la implementación original, con el fin de subsanar el hecho de que los rasgos de selección (y todos los rasgos) constituyen un conjunto, y por lo tanto no están ordenados. +#### Categorías funcionales +Para indicar que un item léxico es una categoría funcional se necesitan dos cosas: 1) el contenido del rasgo fonético debe aparecer entre corchetes, y 2) la categoría del primer elemento con el que se combina debe estar precedida por una barra. La **barra al comienzo** (`/V`) permite que el algoritmo sepa en qué momento de la derivación debe seleccionarse la categoría funcional. Por ejemplo, así quedaría la entrada léxica de un *v* que se pudiera combinar con *llegó*: +``` + + [] + + v + /V + D/ + + none + +``` +Dada esta gramática, una oración como *El perro llegó* pasaría por las siguientes etapas: una vez satisfechos los rasgos de selección del *V*, el *v* es seleccionado, luego se combina por merge externo con el *V*, y finalmente se combina por merge interno con el *D*, dando lugar a la siguiente estructura: + +![](https://img001.prntscr.com/file/img001/YnnsxD8ASmy8ea0zR2ilIQ.png) + +Como se ve, es posible definir una gramática de tal modo que dos o más categorías funcionales se combinen entre sí. +
También es posible declarar dos ítems con los mismos rasgos fonéticos y categoriales, pero diferentes rasgos de selección. En este caso, el programa generará dos lexical arrays distintos, e imprimirá todas las derivaciones exitosas. +### Implementación +#### Reglas +El programa utiliza un algoritmo bottom-up que se basa en las siguientes reglas (utilizamos el nombre de las variables tal como aparecen en la función `autotf()` del módulo `derivations/parser`. + +| N° | If len(x.triggers) | & len(workspace.w) | & len(lexical_array.the_list) | then: +| ------------- |:-------------:| -------- | --------- | ------- | +| 0 | | | | select(x) +| 1 | 0 | 1 | 0 | *success* +| 2| = 1 (con /) | = 1 | | *internal* merge(x,y) +| 3| | = 1 | > 0 | select(y) +| 4| 0 | = 2 | > 0 | select(z) +| 5| = 0 | = 2 & len(y.triggers) > 0 | | merge(y,x) +| 6| = 1 | = 2 | | merge(x,y) +| 7| = 1 | = 3 | | merge(z,y) + +La numeración de las reglas indica precedencia en cuanto a su aplicación. *x* es el elemento a analizar en cada etapa de la derivación, mientras que *y* es un segundo elemento que puede pertenecer al lexical array (en la regla 2, *y* es el elemento con el índice inmediatamente superior a *x*, salvo cuando se trata de una categoría funcional) o al workspace, e incluso estar contenido en *x* (en el caso del merge interno). El primer valor que toma *x* es el *lexical item token* correspondiente a la última palabra de la oración (i.e., que coincide con sus rasgos fonéticos), el cual aparece indexado en el *lexical array* con el número 0 (`x.idx = 0`). +La derivación falla cuando su último estado no coincide con ninguna de las posibilidades ilustradas en la tabla, o cuando falla el merge (si el rasgo de selección de *x* no coincide con el de *y*, o si coincide pero *y* tiene rasgos de selección todavía sin satisfacer). + +#### Transfer +La operación Transfer, tal como se encuentra definida en el trabajo de Collins-Stabler, interviene en el pasaje entre la estructura sintáctica y las formas fonética (TransferPF) y semántica (TransferLF). Nuestro programa implementa una versión simplificada de TransferPF, que se aplica a toda derivación exitosa mediante la función recursiva `transfer()`. Este algoritmo opera de la siguiente manera: + +* Si *x* e *y* son ítems léxicos (i.e., objetos sintácticos simples), se añaden a la lista `transferred_sentence` sus respectivos rasgos `Phon`, en este orden: primero el elemento con rasgos de selección (es decir, el núcleo) y luego el elemento seleccionado por aquél (que será el complemento). +* Si *x* es núcleo pero, al mismo tiempo, un conjunto de objetos sintácticos (i.e., un objeto sintáctico complejo), entonces se añade a la lista en primer lugar el rasgo `Phon` de *y* (especificador), en caso de que éste sea un item léxico, y luego se aplica nuevamente `transfer()` sobre *x*. En cambio, si *y* también es un objeto complejo, entonces se aplica `transfer()` primeramente a *y*, y luego a *x*. + +Mediante estas reglas el programa obtiene la forma lineal de la estructura sintáctica generada, y luego evalúa si coincide con la de la oración introducida por el usuario. En caso afirmativo, la derivación es exitosa. + +#### Categorías funcionales +Las categorías funcionales se añaden mediante la función `add_functional_categories()`. Sencillamente, aquí el algoritmo revisa la lista de ítems y evalúa si para cada elemento (sea funcional o léxico) existe una categoría funcional que lo seleccione. En caso afirmativo, se añade en último lugar a la lista con la que se elaborará el *lexical array*. +#### Manejo de ítems duplicados +Cuando dos o más entradas léxicas coinciden en sus rasgos fonéticos y categoriales, la función `get_possible_lexicons()` genera todas las combinaciones posibles de ítems, de tal modo que en cada combinación no haya más que un ítem con dichos rasgos. Estas listas son recogidas luego por la función `parse()`, que produce tantas derivaciones como listas haya. Finalmente se imprimen todas las derivaciones exitosas. En `data/lexicon.xml` hay dos `v` con distintos rasgos, uno para la oración inacusativa (con merge interno) y otro para las oraciones transitiva e inergativa. + +### Problemas conocidos +La mayoría de los escollos que presenta este parser se deben a que utiliza la forma fonética (de la oración-input) para reconstruir la estructura sintáctica: un camino inverso al que proponen las gramáticas generativas. Y si bien la implementación de *transfer* subsana desde el punto de vista teórico esta limitación, lo cierto es que la estructura sintáctica se mantiene atada a la linealidad de la frase, en la medida en que los dos primeros elementos (empezando desde la derecha) siempre intentarán combinarse entre sí, o a lo sumo se seleccionará un tercero (regla 4). Esto vuelve imposible el reconocimiento de -por ejemplo- oraciones interrogativas o focalizadas donde el elemento sintáctico de la derecha (el primero en combinarse) se realiza luego en el margen izquierdo de la oración. Y asimismo dificulta el análisis de oraciones ditransitivas (como *el perro entregó el hueso al dueño*, en las que el OD ocuparía la posición de especificador y el OI, la de complemento. En nuestra implementación, en cambio, estas posiciones aparecen invertidas, debido al orden en que se da el merge. + +![](https://img001.prntscr.com/file/img001/yzdTEc4TQW-G-MTzhEvQsQ.png) + +Nótese que `12,V` es un objeto complejo, y que por lo tanto en `13,V`, `10,P` es especificador. Esto supone que la oración generada por `transfer()` será *el perro al dueño entregó el hueso*. A su vez, un merge interno entre `12,V` y `13,V` resulta imposible, ya que el primer objeto tiene rasgos de selección sin satisfacer. El objeto `14,V` sí podría combinarse con `13,V`, pero sólo después del merge externo con `15,D`, por lo que el sintagma verbal quedaría por encima del argumento externo. De todos modos, un Lexicon con rasgos de selección y/o categorías funcionales distintas podría llegar a dar cuenta de esta oración sin necesidad de modificar el código. + +### A futuro +Elegimos el parser bottom-up porque es el que mejor se adapta a la implementación y el formalismo originales. Tal vez sería posible mejorarlo mediante la adición de nuevos operadores (como una o varias categorías iniciales), y permitiendo que se combinen elementos independientemente del orden lineal. + +Otra alternativa sería recurrir a un parser top-down capaz de generar todas las oraciones posibles de una gramática, y luego evaluar si la oración ingresada por el usuario coincide con alguna de ellas. Para esto, podríamos seguir el mecanismo descrito por [Shieber et al. (2005)](https://arxiv.org/abs/cmp-lg/9404008): +1. Initialize the chart to the empty set of items and the agenda to the axioms of the deduction system. +2. Repeat the following steps until the agenda is exhausted: + * a) Select an item from the agenda, called the trigger item, and remove it. + * b) Add the trigger item to the chart, if the item is not already in the chart. + * c) If the trigger item was added to the chart, generate all items that can be derived from the trigger item and any items in the chart by one application of a rule of inference, and add these generated items to the agenda. +3. If a goal item is in the chart, the goal is proved, i.e., the string is recognized, otherwise it is not. + +Queda por ver cuán compatible sería esta solución con la propuesta de Collins-Stabler. diff --git a/data/lexicon.xml b/data/lexicon.xml index 52848ee..31f1900 100644 --- a/data/lexicon.xml +++ b/data/lexicon.xml @@ -1,34 +1,107 @@ - K8 + corrió - N + V none - runs + comió V - N + D + + none + + + llegó + + V + D + D/ none - ate + entregó V - N + D + P + + none + + + hueso + + N + + none + + + el + + D N none - apples + al + + P + N + + none + + + perro N none + + dueño + + N + + none + + + [] + + v + /V + D + + none + + + [] + + v + /V + D/ + + none + + + [] + + T + /v + + none + + + [] + + C + /T + + none + \ No newline at end of file diff --git a/data/lexicon_en.xml b/data/lexicon_en.xml new file mode 100644 index 0000000..8bd4476 --- /dev/null +++ b/data/lexicon_en.xml @@ -0,0 +1,107 @@ + + + + run + + V + + none + + + ate + + V + D + + none + + + arrived + + V + D + D/ + + none + + + gave + + V + D + P + + none + + + bone + + N + + none + + + the + + D + N + + none + + + to + + P + D + + none + + + dog + + N + + none + + + owner + + N + + none + + + [] + + v + /V + D + + none + + + [] + + v + /V + D/ + + none + + + [] + + T + /v + + none + + + [] + + C + /T + + none + + \ No newline at end of file diff --git a/derivations/__init__.py b/derivations/__init__.py index 00c0886..d80bd89 100644 --- a/derivations/__init__.py +++ b/derivations/__init__.py @@ -1,3 +1,4 @@ -__all__ = ["Derivation"] +__all__ = ["Derivation","parse","phrase_to_list","word_to_lex"] -from .derivation import * \ No newline at end of file +from .derivation import * +from .parser import * \ No newline at end of file diff --git a/derivations/parser.py b/derivations/parser.py new file mode 100644 index 0000000..14cabd8 --- /dev/null +++ b/derivations/parser.py @@ -0,0 +1,275 @@ +import re +from structures import * +from derivations import derivation + +class Derivation(derivation.Derivation): + + successfull_derivation = None + sentence_to_parse = '' + transferred_sentence = [] + + def transfer(self,so): + list_from_set = list(so.syntactic_object_set) + x,y = list_from_set[0],list_from_set[1] + W = list(self.stages[-1].workspace.w)[0] + + empty_li = LexicalItem({Cat_Feature('E')},{Sem_Feature('')},{Phon_Feature("")}) + self.i_lang.lexicon.lex.add(empty_li) + empty_so = LexicalItemToken(empty_li,0) + if x.is_final(y,W) == False: + x = empty_so + if y.is_final(x,W) == False: + y = empty_so + + if isinstance(x,LexicalItemToken) and isinstance(y,LexicalItemToken): + if len(x.triggers) != 0: # if head + self.transferred_sentence.append(list(x.lexical_item.phon)[0].label) + self.transferred_sentence.append(list(y.lexical_item.phon)[0].label) + else: # if complement + self.transferred_sentence.append(list(y.lexical_item.phon)[0].label) + self.transferred_sentence.append(list(x.lexical_item.phon)[0].label) + elif isinstance(x,SyntacticObjectSet) and isinstance(y,SyntacticObjectSet): + if len(x.triggers) != 0: # if head + self.transfer(y) + self.transfer(x) + else: # if specifier + self.transfer(x) + self.transfer(y) + else: + if isinstance(x,LexicalItemToken) and isinstance(y,SyntacticObjectSet): + self.transferred_sentence.append(list(x.lexical_item.phon)[0].label) + self.transfer(y) + elif isinstance(y,LexicalItemToken) and isinstance(x,SyntacticObjectSet): + self.transferred_sentence.append(list(y.lexical_item.phon)[0].label) + self.transfer(x) + + def autotf(self, index, debug = False): + if debug == True: + self.print_derivation() + x = self.stages[-1].workspace.find_workspace(index) # get x + triggers = [trigger for trigger in x.triggers] + trigger_labels = [trigger.label for trigger in triggers] + + # Rule 1 - Successful derivation + if len(x.triggers) == 0 and len(self.stages[-1].workspace.w) == 1 and len(self.stages[-1].lexical_array.the_list) == 0: + if debug == True: + print('Aplying transfer') + self.transferred_sentence = [] + self.transfer(x) + self.transferred_sentence = [word for word in self.transferred_sentence if word != '' and not word.startswith('[')] + self.transferred_sentence = ' '.join(self.transferred_sentence) + if debug == True: + print('Transferred sentence:',self.transferred_sentence) + if self.transferred_sentence == self.sentence_to_parse: + if debug == True: + self.print_derivation() + print("Successfull parsing!") + print('') + self.successfull_derivation = self + return True + else: + return None + + if len(triggers) > 1 and len(self.stages[-1].workspace.w) == 1 and len(self.stages[-1].lexical_array.the_list) == 0 and isinstance(x,SyntacticObjectSet): + return None + + # Rule 2 - Internal merge + if len(triggers) == 1 and triggers[0].label[-1] == '/' and len(self.stages[-1].workspace.w) == 1 and isinstance(x, SyntacticObjectSet): + #x.lexical_item.copy_features() + triggers[0].label = triggers[0].label[0:-1] + if debug == True: + print('Aplying rule 2: internal merge (X,Y)') + print('') + for y in x.syntactic_object_set: + if y.category.label == triggers[0].label: + self.automerge(index,y.idx, debug=debug) + return True + elif isinstance(y, SyntacticObjectSet): + for z in y.syntactic_object_set: + if z.category.label == triggers[0].label: + y = z + self.automerge(index,y.idx,debug=debug) + return True + return None + + # Rule 3 - Select + if len(self.stages[-1].workspace.w) == 1 and len(self.stages[-1].lexical_array.the_list) > 0: + if debug == True: + print('Aplying rule 3: select(Y)') + print('') + for li in self.stages[-1].lexical_array.the_list: + if (list(li.lexical_item.phon)[0]).label.startswith('[') and len(x.triggers) == 0: + triggers_fc = [re.sub("/","",trigger.label) for trigger in li.triggers if trigger.label.startswith('/')] + if x.category.label in triggers_fc: + for trigger in li.triggers: + if trigger.label.startswith("/"): + trigger.label = re.sub("/","",trigger.label) + self.autoselect(li.idx,debug) + return True + + list_of_idxs = [y.idx for y in self.stages[-1].lexical_array.the_list] + self.autoselect(min(list_of_idxs),debug) + return True + + # Rule 5 - External merge (Y,X) + if len(triggers) == 0 and len(self.stages[-1].workspace.w) == 2: + if debug == True: + print('Aplying rule 5: external merge (Y,X)') + print('') + for y in self.stages[-1].workspace.w: + triggers_y = [trigger.label for trigger in y.triggers] + if len(triggers_y) > 0 and x.category.label in triggers_y: + self.automerge(y.idx,index,debug=debug) + return True + # Rule 4 + if len(self.stages[-1].lexical_array.the_list) > 0: + if debug == True: + print('Aplying rule 4: select(Z)') + print('') + index_z = min([y.idx for y in self.stages[-1].lexical_array.the_list]) + self.autoselect(index_z,debug) + return True + return None + + # Rule 6 - External merge (X,Y) + if len(triggers) > 0 and len(self.stages[-1].workspace.w) == 2: + if debug == True: + print('Aplying rule 6: external merge (X,Y)') + print('') + for y in self.stages[-1].workspace.w: + if y.category.label in trigger_labels: + self.automerge(index,y.idx,debug=debug) + return True + return None + + # Rule 7 - External merge + if len(x.triggers) > 0 and len(self.stages[-1].workspace.w) == 3: + if debug == True: + print('Aplying rule 7: external merge(Z,Y)') + print('') + z = self.stages[-1].workspace.find_workspace(index-1) + if z in self.stages[-1].workspace.w: + self.automerge(index,index-1,debug=debug) + return True + else: + z_idx = max([so.idx for so in self.stages[-1].workspace.w if so!=x]) + self.automerge(index,z_idx,debug=debug) + return True + + def autoselect(self, index, debug): + last_stage = self.stages[-1] + lexical_item_token = last_stage.lexical_array.find_lexical_array(index) + new_stage = last_stage.select_stage(lexical_item_token) + self.stages.append(new_stage) + if self.autotf(index,debug=debug) == None: + if debug == True: + print('Derivation failed') + print('') + return + + def automerge(self, idx1, idx2, debug): + last_stage = self.stages[-1] + try: + new_stage = last_stage.merge_stage(idx1, idx2) + except InteractionError: + if debug == True: + print('Derivation failed') + return + self.stages.append(new_stage) + #print('Stage: ',len(self.stages)) + index = max([x.idx for x in last_stage.workspace.w]) + if self.autotf(index,debug=debug) == None: + if debug == True: + print('Derivation failed') + print('') + return + + def autoderive(self, sentence, debug): + self.sentence_to_parse = sentence + self.autoselect(0, debug) + +def word_to_lex(lexicon_list,word): + """ Matches a word with its lexical items + Return None if word is not in lexicon """ + lexical_item = None + for x in lexicon_list: + if (list(x.phon))[0].label == word: + lexical_item = x + return lexical_item + +def phrase_to_list(lexicon_list, phrase): + """ Param: phrase (str) + Return: list of lexical_item """ + phrase = phrase.split() + new_list = [word_to_lex(lexicon_list, word) for word in phrase] + new_list.reverse() + return new_list + +def add_functional_categories(word_list,functional_list): + words_with_fc = [] + for word in word_list: + if word == None: + raise AssertionError + category = [c.label for c in word.syn if isinstance(c,Cat_Feature)] + for fcw in functional_list: + triggers = [re.sub("/","",t.label) for t in fcw.syn if isinstance(t,Trigger_Feature) and t.label.startswith('/')] + if category[0] in triggers: + if fcw not in word_list and word not in words_with_fc: + word_list.append(fcw) + words_with_fc.append(word) + return word_list + +def get_possible_lexicons(lexicon): + import itertools + duplicates = [] + for item1 in lexicon.lex: + phon1 = [ph.label for ph in item1.phon if isinstance(ph,Phon_Feature)][0] + category1 = [c.label for c in item1.syn if isinstance(c,Cat_Feature)][0] + for duplicate in duplicates: + for item3 in duplicate: + phon3 = [ph.label for ph in item3.phon if isinstance(ph,Phon_Feature)][0] + category3 = [c.label for c in item3.syn if isinstance(c,Cat_Feature)][0] + if phon1 == phon3 and phon1 == category3 and item1 not in duplicate: + duplicate.append(item1) + pass + for item2 in lexicon.lex: + phon2 = [ph.label for ph in item2.phon if isinstance(ph,Phon_Feature)][0] + category2 = [c.label for c in item2.syn if isinstance(c,Cat_Feature)][0] + if phon1 == phon2 and category1 == category2 and item1 != item2: + if not any(item1 in list for list in duplicates): + duplicates.append([item1,item2]) + all_fc_possibilities = list(itertools.product(*duplicates)) + lexicon_list = [item for item in lexicon.lex if not any(item in list for list in duplicates)] + all_possibilities = [] + for possibility in all_fc_possibilities: + new_list = [item for item in possibility] + [item for item in lexicon_list] + all_possibilities.append(new_list) + return all_possibilities + +def parse(sentence,filename="lexicon.xml",debug=True): + sentence = ' '.join(((re.sub("[\.\,\!\?\:\;\-\=¿¡\|\(\)#\[\]\"]", "", sentence).lower()).split())) + lexicon = Lexicon(filename) + possible_lexicons = get_possible_lexicons(lexicon) + ug = UniversalGrammar(set(), set(), set()) + i_lang = ILanguage(lexicon, ug) + successfull_derivations = [] + for i in range(len(possible_lexicons)): + word_list = phrase_to_list(possible_lexicons[i], sentence) + functional_list = [item for item in possible_lexicons[i] if (list(item.phon))[0].label.startswith('[')] + word_list = add_functional_categories(word_list,functional_list) + deriv = Derivation(i_lang, word_list=word_list) + deriv.autoderive(sentence, debug) + if deriv.successfull_derivation != None: + successfull_derivations.append(deriv.successfull_derivation) + + if len(successfull_derivations) > 0: + print('') + print(len(successfull_derivations),"derivation(s)") + print('') + for derivation in successfull_derivations: + derivation.print_derivation() + print('') + else: + print('') + print('Sentence is not derivable.') + print('') diff --git a/main.py b/main.py index 80386f7..69a6db7 100644 --- a/main.py +++ b/main.py @@ -1,20 +1,68 @@ from derivations import * from structures import * +import os -def main(): - lexicon = Lexicon() +filename = "lexicon.xml" + +def print_grammars(): + global filename + full_path = os.path.abspath("data") + print(full_path) + filenames = [f for f in os.listdir(full_path) if os.path.isfile(os.path.join(full_path, f))] + for f in filenames: + if f == filename: + filenames.pop(filenames.index(f)) + filenames.insert(0, f) + indexes = {i:f for i,f in enumerate(filenames)} + print(f'{len(indexes)} grammar(s) found: ') + for key,value in indexes.items(): + if key == 0: + print(f"{key}: '{value}' (current)") + else: + print(f"{key}: '{value}'") + return indexes + +def manual_derivation(filename=filename): + lexicon = Lexicon(filename) counter = 0 ug = UniversalGrammar(set(), set(), set()) # todo: add feature import to UG i_lang = ILanguage(lexicon, ug) derivation = Derivation(i_lang, word_list=list(i_lang.lexicon.lex)) derivation.derive() - - # kate_token1 = LexicalItemToken(list(lexicon.lex)[0], 0) - # kate_token2 = LexicalItemToken(list(lexicon.lex)[0], 1) - # kate_runs = kate_token1.merge(kate_token2, 2) - # tr = tree(kate_runs) - # tr.pretty_print() - +def main(): + global filename + debug = False + enable = 'Enable' + while True: + new_line = "\n" + user_input = input(f'Select an option and press enter:{new_line*2}1 - Parser{new_line}2 - Manual derivation{new_line}3 - Change default grammar{new_line}4 - {enable} stage-by-stage view{new_line}5 - Quit{new_line*2}') + if user_input == '1': + sentence = input(f'{new_line}Insert a sentence to parse:{new_line*2}') + try: + parse(sentence,filename,debug) + except AssertionError: + print(f"{new_line}[ERROR] Some words are not in the lexicon{new_line}") + elif user_input == '2': + manual_derivation(filename) + elif user_input == '3': + while True: + indexes = print_grammars() + file_index = input(f'Select the index of the new grammar:{new_line}') + try: + filename = indexes[int(file_index)] + break + except: + print(f'{new_line}[ERROR] Insert a valid option{new_line}') + elif user_input == '4': + debug = not debug + if debug == False: + enable = "Enable" + else: + enable = 'Disable' + elif user_input == '5': + break + else: + print('Select a valid option (1-4)') main() \ No newline at end of file diff --git a/structures/__init__.py b/structures/__init__.py index ddb8a71..302e7d3 100644 --- a/structures/__init__.py +++ b/structures/__init__.py @@ -1,7 +1,6 @@ -__all__ = ["Feature", "Syn_Feature", "Sem_Feature", "Cat_Feature", "Sel_Feature", "Phon_Feature", +__all__ = ["Feature", "Syn_Feature", "Sem_Feature", "Cat_Feature", "Sel_Feature", "Phon_Feature","Trigger_Feature", "UniversalGrammar", "ILanguage", "SyntacticObject", "SyntacticObjectSet", "LexicalItem", - "LexicalArray", "Workspace", "Stage", - "Lexicon", "LexicalItemToken", "tree", "InteractionError"] + "LexicalArray", "Workspace", "Stage","Lexicon", "LexicalItemToken", "tree", "InteractionError"] from .definitions import * from .features import * diff --git a/structures/lexicon.py b/structures/lexicon.py index 5af8620..24fbf92 100644 --- a/structures/lexicon.py +++ b/structures/lexicon.py @@ -11,10 +11,13 @@ class Lexicon(object): """ Definition 3: A lexicon is a finite set of lexical items. """ - def __init__(self): + def __init__(self,file_name=None): self.lex: Set[LexicalItem] = set() # get the file path to lexicon data - file_name = "lexicon.xml" + if file_name == None: + file_name = "lexicon.xml" + else: + file_name = file_name full_file = os.path.abspath(os.path.join("data", file_name)) # create xml tree dom = ElementTree.parse(full_file) @@ -32,4 +35,4 @@ def __init__(self): cat_set = set([Cat_Feature(f) for f in w.find('syn/cat').text.strip().split()]) syn_set = sel_set.union(cat_set) new_word = LexicalItem(syn_set, sem_set, phon_set) - self.lex.add(new_word) + self.lex.add(new_word) \ No newline at end of file diff --git a/structures/syntactic_objects.py b/structures/syntactic_objects.py index ae810e7..c396827 100644 --- a/structures/syntactic_objects.py +++ b/structures/syntactic_objects.py @@ -180,6 +180,15 @@ def sister_finder(self, container, sisterset = set()): else: return False + def is_final(self, syster, workspace): + if self.c_commands(self, workspace): + if syster.immediately_contains(self): + return True + else: + return False + else: + return True + def is_derivable(self, lexicon): # Not implemented """ DEFINITION 15: @@ -266,9 +275,10 @@ class LexicalItemToken(SyntacticObject): """ def __init__(self, lexical_item: LexicalItem, idx: int): super(LexicalItemToken, self).__init__(idx) - self.lexical_item = lexical_item - self.triggers = { f for f in self.lexical_item.syn if type(f) is Trigger_Feature } - cat_features = { f for f in self.lexical_item.syn if type(f) is Cat_Feature } + import copy + self.lexical_item = copy.deepcopy(lexical_item) + self.triggers = { copy.deepcopy(f) for f in self.lexical_item.syn if type(f) is Trigger_Feature } + cat_features = { copy.deepcopy(f) for f in self.lexical_item.syn if type(f) is Cat_Feature } assert len(cat_features) == 1 (category, ) = cat_features self.category = category