diff --git a/.gitignore b/.gitignore index 4acafde18..ea399309b 100644 --- a/.gitignore +++ b/.gitignore @@ -408,3 +408,4 @@ dmypy.json # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) +/src/code.cl diff --git a/doc/Readme.md b/doc/Readme.md index 3b2569f5c..a01d8c49a 100644 --- a/doc/Readme.md +++ b/doc/Readme.md @@ -1,33 +1,441 @@ -# Documentación +# Informe de Complementos de Compilación +## Compilador de Cool +*** -## Readme -Modifique el contenido de este documento para documentar de forma clara y concisa los siguientes aspectos: +## Autores +- Claudia Olavarrieta Martínez +- Marcos Adrián Valdivié Rodríguez +- Adrián Hernández Pérez -- Cómo ejecutar (y compilar si es necesario) su compilador. -- Requisitos adicionales, dependencias, configuración, etc. -- Opciones adicionales que tenga su compilador. +## Uso del compilador -## Sobre los Equipos de Desarrollo +**Requerimientos** -Para desarrollar el compilador del lenguaje COOL se trabajará en equipos de 2 o 3 integrantes. El proyecto de Compilación será recogido y evaluado únicamente a través de Github. Es imprescindible tener una cuenta de Github para cada participante, y que su proyecto esté correctamente hosteado en esta plataforma. +Para ejecutar el proyecto es necesario instalar Python y las dependencias que +se listan en requirements.txt -**⚠️ NOTA**: Debe completar el archivo `team.yml` con los datos correctos de cada miembro de su equipo. +Para instalar las dependencias puede ubicarse en la carpeta del proyecto y ejecutar -## Sobre los Materiales a Entregar +```bash +pip install -r requirements.txt +``` -Para la evaluación del proyecto Ud. debe entregar un informe en formato PDF (`report.pdf`) en esta carpeta, que resuma de manera organizada y comprensible la arquitectura e implementación de su compilador. -El documento no tiene límite de extensión. -En él explicará en más detalle su solución a los problemas que, durante la implementación de cada una de las fases del proceso de compilación, hayan requerido de Ud. especial atención. +**Ejecución** + +Para ejecutar el proyecto debe situarse en la carpeta *src* y utilizar alguno de los +dos comandos que se muestran a continuación: +```bash +python Main.py -f path/to/file.cl +python Main.py --file path/to/file.cl +``` + +Como salida se guarda en un fichero del mismo nombre del introducido como parámetro +con extensión *.mips* el código compilado y listo para ejecutarse. + +## Arquitectura del compilador + +El compilador está compuesto por distintos módulos que son los encargados de tomar +el código escrito inicialmente en COOL y obtener el resultado final en código MIPS que +permita su ejecución. + +## Organización + +La estructura de archivos del proyecto es la siguiente: + +``` +cool-compiler-2021 +|___doc + img + src + |__Main.py + core + |__cil + |__BaseCoolToCilVisitor.py + CilAst.py + CoolToCilVisitor.py + |__cool + |__CoolAst.py + CoolAstFormatter.py + |__lexer + |__Lexer.py + |__mips + |__CilToMipsVisitor.py + MipsBasics.asm + MipsAst.py + MipsAstFormatter.py + |__parser + |__Parser.py + |__semantic + |__TypeBuilder.py + TypeChecker.py + TypeCollector.py + |__tools + |__Automata.py + Errors.py + Evaluation.py + FirstAndFollows.py + ParserLR1.py + Parsing.py + Pycompiler.py + Semantic.py + Utils.py + visitor.py + + +``` + +Se omitieron algunos archivos pues no son relevantes en la implementación del compilador. + +### Fases (_Pipeline_) + +El fichero _Main.py_ contiene el pipeline de ejecución del compilador +1. Lexer +2. Parser +3. Recolección de Tipos +4. Construcción de Tipos +5. Chequeo de Tipos +6. COOL a CIL +7. CIL a Mips +8. MIPS a representación como string +9. Escritura en el archivo final + +#### Lexer + +El Lexer es el encargado de dado un string con el código del programa COOL separar el +mismo en tokens para luego ser usado por el parser. En esta fase se utilizó el paquete +_ply_ el cual contiene herramientas de tokenización. Se definieron las expresiones +regulares y símbolos que correspondían a los tokens de la gramática, definiendo reglas +especiales para poder también reconocer los string y comentarios anidados. Además, +se almacena por cada Token la línea y la columna correspondiente en el código, +lo que permite tener mayor información en los mensajes de error y para nuestro uso +en la depuración. + + + +#### Parser + +El Parser define la estructura que tendrá el Árbol de Sintaxis Abstracta (AST) del lenguaje COOL, además +de la gramática que se usará para parsear. +El archivo donde se definen los símbolos y producciones de la gramática puede verse al final +de este fichero en la sección Gramática. + + +Se utilizó un parser LR1 que había sido implementado y probado en proyectos anteriores +de la asignatura. Fue necesaria la inclusión de nuevas reglas y la edición de algunas anteriores para la +solución de problemas con la precedencia de los operadores y la ambigüedad de la gramática. +Durante esta fase se reconocen y reportan los errores léxicos del programa. + + +#### Recolección de tipos + +En esta fase se recorren todas las declaraciones de clases, se crean los tipos asociados +y se valida que no se estén redefiniendo estos. Primeramente, se añaden los tipos builtin +(Object, IO, Bool, String, Int) y luego los tipos definidos por el usuario, revisando +que no existan nombres de clases repetidos. + +#### Construcción de Tipos + +En esta fase se recorren nuevamente las declaraciones de clases añadiendo los métodos y +atributos de cada clase. Se define la herencia para cada clase, en caso de que no exista se hereda de la +clase Object. Además, se revisa que exista una clase Main con un método main que indica el inicio +de la ejecución del programa COOL. + +#### Chequeo de tipos + +En esta fase se hace el chequeo semántico de cada tipo. Se evalúa para cada expresión su tipo de retorno +y se valida que estos cumplan las reglas semánticas definidas en el lenguaje. Además, se chequea nuevamente +las clases definidas por el usuario en busca de la existencia de herencia cíclica. +Algunos de los errores que se chequean en esta fase son: +- Las operaciones aritméticas solo están definidas para el tipo Int. +- Que las variables y los métodos hayan sido definidos previamente a su uso. +- Que no exista redefinición de métodos o atributos en las clases. +- Que no exista herencia cíclica. +- Que las palabras claves self y SELF_TYPE sean usadas correctamente. +- Que las funciones de clases sean llamadas con la cantidad correcta de parámetros. +- Que el objeto empleado en cada expresión sea compatible con el tipo declarado para la misma. + +#### COOL a CIL + +Durante esta fase se realiza la conversión del lenguaje COOL a un lenguaje intermedio(CIL). +El fichero CilAst contiene la definición de las clases usadas para conformar el AST del lenguaje CIL. +En el fichero BaseCooltoCilVisitor se definen los métodos básicos para registrar +una variable, parámetro, función y atributo, entre otros. Además se +registran los tipos builtin, es decir, se escriben en código CIL las instrucciones +para registrar los tipos Object, IO, String, Int y Bool, así como las funciones y atributos de cada uno de estos.. +El fichero CoolToCilVisitor es el encargado de transformar el AST de COOL en un AST de CIL, para facilitar +luego la traducción de este al lenguaje MIPS. Este fichero cuenta con un visitor que se encarga de transformar +cada nodo del AST de un lenguaje a otro, algunos de los aspectos a destacar son: +- En el visitor del ProgramNode se define la función entry, que es por donde se comenzará la ejecución del +programa MIPS. +- En el visitor del ClassDeclarationNode se definen las funciones init e init_attr corespondientes a cada +clase, estas funciones son las encargadas de reservar la memoria necesaria para cada uno de los tipos +definidos por el usuario. +- Especial énfasis en el visitor del CaseOfNode, este se encarga de generar todas las instrucciones +necesarias para validar correctamente la rama que debe ejecutarse, o mostrar un error en tiempo de ejecución +en caso de no haber ninguna válida. Para lograr esto primero se ordenan los tipos involucrados en las ramas del +case según su profundidad en el árbol de herencia del programa, de mayor a menor. Luego se visitan estos, +revisando para cada uno todos sus descendientes en dicho árbol de herencia y comprobando, en tiempo de +ejecución, si estos coinciden con el tipo del objeto que se está analizando. El primer objeto para el que +se halle una correspondencia define la rama por la que debe continuar la ejecución del programa. +- En el visitor del DivNode se añaden las instrucciones necesarias para verificar que el divisor es distinto +de cero, y lanzar un error en tiempo de ejecución en caso contrario. +- El visitor del EqualNode se encarga de revisar primeramente los tipos de los objetos que están siendo +comparados, Int-Int, String-String y Bool-Bool son comparados por valor, mientras que cualquier otro par +es comparado por referencia. +- El visitor del FunctionCallNode se encarga de comprobar que el objeto al cual se le hace el dispatch sea +distinto de Void y mostrar un error en ejecución en caso contrario. + +#### CIL a MIPS +Esta es la fase final donde se traduce de CIL al lenguaje MIPS que da la salida del programa. +Dentro del fichero mips_basics.asm se encuentran algunas funciones predefinidas en mips: malloc, copy, +read_string, equal_string, length, substring y concat. +El fichero MIPSAst contiene la definición de las clases necesarias para representar el código MIPS. +El fichero CilToMipsVisitor visita cada nodo del AST de CIL y lo traduce a sus correspondientes +instrucciones en codigo Mips. Gran dificultad trajo en esta fase el uso correcto de las tablas de dispatch +y los atributos de clase en combinación con la herencia, haciendo necesaria una especificación sobre la +**representación en memoria** que tendría cada objeto. Sobre esto útimo podemos explicar que se decidió representar +los objetos como: +- Marca de clase (4 bytes): Un entero usado para identificar cada tipo del programa. +- Tamaño (4 bytes): Un entero empleado para representar el tamaño, en doble palabras, de la representación del objeto +en memoria. +- Puntero a la tabla de dispatch (4 bytes): Un entero que representa la posición en memoria donde se encuentra +la tabla de dispatch del objeto. +- Definición de atributos de la clase padre. +- Definición de atributos de la clase hijo. +Primero son definidos los atributos de la clase padre de forma recursiva, +luego son definidos los atributos de la clase hijo, colocando estos de forma ordenada según los nombres que tenían +en el código COOL inicial. +Las tablas de dispatch de cada tipo se definen de manera similar, primero las direcciones de memoria correspondientes +a las funciones de las clases padres, o a las de la clase hijo en caso de que hayan sido redefinidas, y luego las +direcciones de memoria de las funciones de la clase hijo, ordenadas alfabéticamente según sus nombres iniciales. +Finalmente las funciones init e init_attr de la clase correspondiente. Cada dirección de memoria corresponde a un +entero de 32 bits. El orden de las funciones en la tabla de dispatch inicia por las del padre para poder ejecutar +correctamente el llamado a una función redefinida en un objeto del tipo hijo cuando este es tratado como un +objeto del tipo padre (polimorfismo). + +Finalmente, el fichero MipsAstFormatter es el encargado de transformar el AST de MIPS a formato string para +luego escribir este en el archivo final. + +# Invocación de métodos virtuales +Para mostrar como se resolvió la invocación de métodos virtuales usaremos un ejemplo. Supongamos que tenemos una clase A +que posee las funciones foo1 y foo2, y una clase B que hereda de A que posee las funciones foo1 (Sobreescribiendo a foo1 +en A) y foo3. + +El código MIPS a continuación corresponde a la plantilla que se genera para las clases A y B respectivamente cuando +el programa es compilado. + +```MIPS +type_6_dispatch: + .word function_abort_at_Object + .word function_copy_at_Object + .word function_type_name_at_Object + .word function_foo1_at_A + .word function_foo2_at_A + .word __init_attr_at_A + .word __init_at_A + +type_6_prototype: + .word 5 + .word 4 + .word type_6_dispatch + .word -1 + + +type_7_dispatch: + .word function_abort_at_Object + .word function_copy_at_Object + .word function_type_name_at_Object + .word function_foo1_at_B + .word function_foo2_at_A + .word function_foo3_at_B + .word __init_attr_at_B + .word __init_at_B + +type_7_prototype: + .word 6 + .word 4 + .word type_7_dispatch + .word -1 +``` + +Al instanciar un objeto se copia la plantilla correspondiente a la representacion en memoria de dicho objeto, es decir, +se copia la sección prototype del objeto, y se instancian cada uno de sus atributos de forma recursiva. La sección +prototype posee la dirección de memoria correspondiente a la tabla de dispatch del objeto. Esta dirección es la que es +usada para hacer los llamados a los métodos de la clase. + +Cada vez que se intente llamar a un método de una clase lo primero que se hace es buscar la dirección de la tabla de +dispatch del objeto al cual se le está haciendo el llamado, y luego el índice del método que se está llamando, +para luego efectuar un jump and link (jal) a la dirección a la que apunta la tabla de dispatch sumando cuatro veces el +índice de dicho método. + +Ahora, observemos que la tabla de dispatch de la clase B posee todos los métodos de la clase A en +el mismo orden que esta, es decir, el offset correspondiente al método foo1 es el mismo para la clase A y para la clase +B. De esta forma si se utiliza un objeto de +tipo B como si fuera de tipo A, llamando a un método del mismo, digamos foo1, entonces el compilador solo tiene que +ocuparse de buscar el índice del método dentro de la clase A y la dirección de la tabla de dispatch del objeto que se +está usando (guardada en la sección de memoria correspondiente a dicho objeto), luego, como el índice del método en A +corresponde con el índice del método sobreescrito en B solo faltaría sumarle dicho offset a la tabla de dispatch para +obtener el método que debe usarse, en este caso ```function_foo1_at_B```. La clase ***DynamicCallNode*** de Cil es la +encargada de manejar la lógica de los llamados a métodos virtuales a objetos cuando se traduce de Cil a Mips. + +La clase ***StaticCallNode*** de Cil es usada explícitamente para los llamados a un método de un tipo en específico +usando la sintaxis ```object@TYPE.method()``` definida en COOL. Esta clase, en vez de buscar la tabla de dispatch +correspondiente al objeto que se está llamando, busca la tabla de dispatch del objeto que se especifica luego del +caracter @, de esta forma, una vez calculado el offset de dicho método, este corresponderá precisamente al método +correspondiente al tipo especificado. + +En conclusión, si se hace ```(new B).foo1()``` se llama al método foo1 en B haciendo uso de la tabla de dispatch del +objeto B que se creó, sin embargo, si se hace ```(new B)@A.foo1()``` se llama al método foo1 en A haciendo uso de la +tabla de dispatch del prototype del objeto A. Mientras que si se hace ```object.foo1()``` en una función que reciba +un parámetro de tipo A, pero a la que se le está pasando un objeto de tipo B, se llamará al método correspondiente en B, +utilizando la tabla de dispatch del objeto que se está pasando como parámetro. + +El índice del método en la tabla de dispatch es calculado en tiempo de compilación usando el índice del método +en el array de métodos de cada tipo recolectado durante la traducción de Cil a Mips. Para que estos índices coincidan +se colocaron, para cada tipo, primeramente los métodos correspondientes al padre, y luego los métodos correspondientes +específicamente al hijo, ordenados estos últimos de forma lexicográfica. + +# Boxing/Unboxing + +Para el proceso de boxing-unboxing se utilizó un mecanismo similar al de las funciones virtuales. Cuando se necesite +acceder a un atributo de un objeto, se toma la dirección en memoria del objeto y el índice de dicho atributo dentro del +objeto, utilizando el índice del nombre del atributo en el +array de attributos recolectado durante la traducción de Cil a Mips. Estos índices son los que se usan siempre que es +necesario obtener o cambiar un atributo a un objeto, y se definen de forma unívoca durante el ya mencionado proceso de +recolección. + +Los atributos de cada objeto se encuentran colocados luego de la dirección de la tabla de dispatch del mismo (tres doble +palabras luego de la dirección de memoria del objeto). Además, el índice de un atributo para una clase hijo coincide con +la del mismo atributo para la clase padre, haciendo posible de esta forma el uso de los atributos durante el polimorfismo. + +Los objetos de tipo Int, Bool y String poseen un atributo llamado _value_ que es usado para guardar el valor del objeto, +en los dos primeros casos, y la dirección de memoria donde se encuentra la cadena de caracteres real, en el tercer caso. +Este atributo es el usado para el trabajo con dichos objetos. + +Para las operaciones aritméticas y de comparación fue necesario tomar el valor real de los objetos que se operan haciéndole +unboxing a los mismos, y luego operando dichos valores directamente. Haciéndole boxing a la respuesta para poder luego +guardarla. Por ejemplo, si se desea comparar dos enteros, estos serán dos objetos en memoria que poseen luego de la tabla +dispatch un entero correspondiente al valor real de los mismos, el proceso de unboxing consiste en tomar dichos valores +y compararlos, para luego instanciar un objeto de tipo Bool y colocarle luego de la tabla de dispatch (en el +atributo value) el resultado. + +# Gramática + +### Terminales +A continuación se muestran los terminales de la gramática, donde entre paréntesis se muestran +los símbolos que corresponden a COOL en los casos en que era requerido: + +- *classx (class), inherits, function* +- *ifx (if), then, elsex (else), fi* +- *whilex (while), loop, pool* +- *let, inx (in)* +- *case, of, esac* +- *semi (;), colon (:), comma (,), dot(.), at (@), opar((), cpar ())* +- *ocur ({), ccur (}), larrow (<-), rarrow (=>)* +- *plus (+), minus (-), star (\*), div (/), isvoid, compl (~)* +- *notx (not), less (<), leq (<=), equal (=)* +- *new, idx (id), typex (type), integer, string, boolx (bool)* + + +### No Terminales + +A continuación se muestran los no terminales de la gramática definida: + +- __program__ +- __class_list, def_class__ +- __feature_list, feature__ +- __param_list, param__ +- __expr_1, expr_2, member_call, expr_list, let_list, case_list__ +- __comp_expr, arith, arith_2, term, factor, factor_2__ +- __atom, func_call, arg_list__ + +### Producciones de la Gramática +A continuación se muestran las producciones de la gramática. En *cursiva* se muestran los terminales +y en **negrita** los no terminales + +__program__ ⟶ __class_list__ + +__class_list__ ⟶ __def_class class_list__ +__class_list__ ⟶ __def_class__ + +**def_class** ⟶ *class* *typex* { **feature_list** } ; +**def_class** ⟶ *class* *typex* *inherits* *typex* { **feature_list** } ; + +**feature_list** ⟶ **feature feature_list** +**feature_list** ⟶ 𝜺 + +**feature** ⟶ *idx* : *typex* ; +**feature** ⟶ *idx* : *typex* <- **expr_1**; +**feature** ⟶ *idx* (**param_list**) : *typex* { **expr_1** } ; +**feature** ⟶ *idx* () : *typex* { **expr_1** }; +**feature** ⟶ **function** *idx* (**param_list**) : *typex* {**expr_1**}; +**feature** ⟶ **function** *idx* () : *typex* {**expr_1**}; + +**param_list** ⟶ **param** +**param_list** ⟶ **param** , **param_list** + +**param** ⟶ *idx* : *typex* + +**expr_1** ⟶ *idx* <- **expr_1** +**expr_1** ⟶ *not* **expr_1** +**expr_1** ⟶ **expr_2** = **expr_1** +**expr_1** ⟶ **expr_2** + +**expr_2** ⟶ **arith** < **arith** +**expr_2** ⟶ **arith** <= **arith** +**expr_2** ⟶ **arith** + +**arith** ⟶ **arith** + **factor** +**arith** ⟶ **arith** - **factor** +**arith** ⟶ **factor** + +**factor** ⟶ **factor** \* **term** +**factor** ⟶ **factor** / **term** +**factor** ⟶ **term** + +**term** ⟶ ~ **term** +**term** ⟶ *isvoid* **term** +**term** ⟶ **atom** + +**atom** ⟶ (**expr_1**) +**atom** ⟶ *string* +**atom** ⟶ *bool* +**atom** ⟶ *idx* +**atom** ⟶ *ifx* **expr_1** *then* **expr_1** *else* **expr_1** *fi* +**atom** ⟶ *whilex* **expr_1** *loop* **expr_1** *pool* +**atom** ⟶ *new* *typex* + +**atom** ⟶ { **expr_list** } +**expr_list** ⟶ **expr_1** ; +**expr_list** ⟶ **expr_1** ; **expr_list** + +**atom** ⟶ *case* **expr_1** *of* **case_list** *esac* +**case_list** ⟶ *idx* : *typex* => **expr_1** ; +**case_list** ⟶ *idx* : *typex* => **expr_1** ; **case_list** + +**atom** ⟶ **func_call** +**func_call** ⟶ **atom** @ *typex* . *idx* (**arg_list**) +**func_call** ⟶ **atom** @ *typex* . *idx* () +**func_call** ⟶ **atom** . *idx* (**arg_list**) +**func_call** ⟶ **atom** . *idx* () + +**func_call** ⟶ *idx* (**arg_list**) +**func_call** ⟶ *idx* () + +**arg_list** ⟶ **expr_list** +**arg_list** ⟶ **expr_list** , **arg_list** + + +## Licencia + +[MIT](https://github.com/NinjaProgrammers/cool-compiler-2021/blob/Proyecto-CMP/LICENSE) + + + + -## Estructura del reporte -Usted es libre de estructurar su reporte escrito como más conveniente le parezca. A continuación le sugerimos algunas secciones que no deberían faltar, aunque puede mezclar, renombrar y organizarlas de la manera que mejor le parezca: -- **Uso del compilador**: detalles sobre las opciones de líneas de comando, si tiene opciones adicionales (e.j., `--ast` genera un AST en JSON, etc.). Básicamente lo mismo que pondrá en este Readme. -- **Arquitectura del compilador**: una explicación general de la arquitectura, en cuántos módulos se divide el proyecto, cuantas fases tiene, qué tipo de gramática se utiliza, y en general, como se organiza el proyecto. Una buena imagen siempre ayuda. -- **Problemas técnicos**: detalles sobre cualquier problema teórico o técnico interesante que haya necesitado resolver de forma particular. -## Sobre la Fecha de Entrega -Se realizarán recogidas parciales del proyecto a lo largo del curso. En el Canal de Telegram se anunciará la fecha y requisitos de cada entrega. diff --git a/doc/team.yml b/doc/team.yml index c16162532..3e667bbea 100644 --- a/doc/team.yml +++ b/doc/team.yml @@ -1,10 +1,10 @@ members: - - name: Nombre Apellido1 Apellido2 - github: github_id - group: CXXX - - name: Nombre Apellido1 Apellido2 - github: github_id - group: CXXX - - name: Nombre Apellido1 Apellido2 - github: github_id - group: CXXX + - name: Marcos Adrian Valdivie Rodriguez + github: mavaldivie + group: C412 + - name: Claudia Olavarrieta Martinez + github: ClaudiOM + group: C411 + - name: Adrian Hernandez Perez + github: AdrianHP + group: C411 diff --git a/requirements.txt b/requirements.txt index 9eb0cad1a..46bf2782f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -pytest -pytest-ordering +ply>=3.11 +pydot>=1.4.2 +pytest>=7.0.1 diff --git a/src/Main.py b/src/Main.py new file mode 100644 index 000000000..d35d66565 --- /dev/null +++ b/src/Main.py @@ -0,0 +1,75 @@ +from core.lexer.Lexer import Lexer +from core.parser.Parser import CoolParser +from core.tools.Evaluation import evaluate_reverse_parse +from core.semantic.TypeCollector import Type_Collector +from core.semantic.TypeBuilder import Type_Builder +from core.semantic.TypeChecker import Type_Checker +from core.cil.CoolToCilVisitor import COOLToCILVisitor +from core.mips.CilToMipsVisitor import CILToMIPSVisitor +from core.mips.MipsAstFormatter import MIPSAstFormatter + + +def main(args): + try: + with open(args.file, 'r') as fd: + code = fd.read() + except Exception as e: + print(f"(0,0) - CompilerError: {e.args}") + print(args.file) + exit(1) + + lexer = Lexer() + tokens = lexer.tokenize(code) + for t in lexer.errors: + print(t) + if any(lexer.errors): exit(1) + + productions, operations = CoolParser(tokens) + for e in CoolParser.errors: + print(e) + if any(CoolParser.errors): exit(1) + COOLast = evaluate_reverse_parse(productions, operations, tokens) + + type_Collector = Type_Collector() + type_Collector.visit(COOLast) + for e in type_Collector.errors: + print(e) + if any(type_Collector.errors): exit(1) + context = type_Collector.Context + + type_Builder = Type_Builder(context) + type_Builder.visit(COOLast) + for e in type_Builder.errors: + print(e) + if any(type_Builder.errors): exit(1) + + type_Checker = Type_Checker(context) + scope = type_Checker.visit(COOLast) + for e in type_Checker.errors: + print(e) + if any(type_Checker.errors): exit(1) + + CILVisitor = COOLToCILVisitor(type_Checker.Context) + CILast = CILVisitor.visit(COOLast, scope) + + MIPSVisitor = CILToMIPSVisitor() + MIPSAst = MIPSVisitor.visit(CILast) + MIPSFormatter = MIPSAstFormatter() + mipsCode = MIPSFormatter.visit(MIPSAst) + + out_file = args.file.split(".") + out_file[-1] = "mips" + out_file = ".".join(out_file) + + with open(out_file, 'w') as f: + f.write(mipsCode) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description='CoolCompiler pipeline') + parser.add_argument('-f', '--file', type=str, default='code.cl', help='file to read') + + args = parser.parse_args() + main(args) diff --git a/src/coolc.sh b/src/coolc.sh index 3088de4f9..cdfddb895 100755 --- a/src/coolc.sh +++ b/src/coolc.sh @@ -1,11 +1,13 @@ -# Incluya aquí las instrucciones necesarias para ejecutar su compilador +#!/bin/bash INPUT_FILE=$1 OUTPUT_FILE=${INPUT_FILE:0: -2}mips # Si su compilador no lo hace ya, aquí puede imprimir la información de contacto -echo "LINEA_CON_NOMBRE_Y_VERSION_DEL_COMPILADOR" # TODO: Recuerde cambiar estas -echo "Copyright (c) 2019: Nombre1, Nombre2, Nombre3" # TODO: líneas a los valores correctos +echo "CMA COOL COMPILER v1.0" +echo "Copyright (c) 2019: Marcos Valdivie, Claudia Olavarrieta, Adrian Hernandez" # Llamar al compilador -echo "Compiling $INPUT_FILE into $OUTPUT_FILE" + +#echo "Compiling $INPUT_FILE into $OUTPUT_FILE" +python3 Main.py -f $INPUT_FILE \ No newline at end of file diff --git a/src/core/cil/BaseCoolToCilVisitor.py b/src/core/cil/BaseCoolToCilVisitor.py new file mode 100644 index 000000000..7da7756b7 --- /dev/null +++ b/src/core/cil/BaseCoolToCilVisitor.py @@ -0,0 +1,302 @@ +from ..cil import CilAst as cil +from ..tools.Semantic import VariableInfo + + +class BaseCOOLToCILVisitor: + def __init__(self, context): + self.dottypes = [] + self.dotdata = [] + self.dotcode = [] + + self.types_map = {} + self.current_type = None + self.current_method = None + self.current_function = None + + self.context = context + self.vself = VariableInfo('self', None) + self.value_types = ['String', 'Int', 'Bool'] + + self.var_names = {} + + self.breakline_data = self.register_data('\n', 0, 0) + self.emptystring_data = self.register_data('', 0, 0) + + @property + def params(self): + return self.current_function.params + + @property + def localvars(self): + return self.current_function.localvars + + @property + def ids(self): + return self.current_function.ids + + @property + def instructions(self): + return self.current_function.instructions + + def register_param(self, vinfo, line, column): + name = f'local_param_{self.current_function.name}_{vinfo.name}_{len(self.params)}' + param_node = cil.ParamNode(name, line, column) + self.params.append(param_node) + self.var_names[vinfo.name] = cil.VarNode(name, line, column) + return self.var_names[vinfo.name] + + def register_local(self, vinfo, line, column): + name = vinfo.name + vinfo.name = f'local_{self.current_function.name}_{vinfo.name}_{len(self.localvars)}' + local_node = cil.LocalNode(vinfo.name, line, column) + self.localvars.append(local_node) + self.var_names[name] = cil.VarNode(vinfo.name, line, column) + return self.var_names[name] + + def register_attribute(self, name, type, line, column): + name = f'attr_{type}_{name}' + return cil.AttributeNode(name, type, line, column) + + + def define_internal_local(self, line, column): + vinfo = VariableInfo('internal', None) + return self.register_local(vinfo, line, column) + + def register_instruction(self, instruction): + self.instructions.append(instruction) + return instruction + + def to_function_name(self, method_name, type_name): + return f'function_{method_name}_at_{type_name}' + + def register_function(self, function_name, line, column): + function_node = cil.FunctionNode(function_name, [], [], [], line, column) + self.dotcode.append(function_node) + return function_node + + def register_type(self, name, line, column): + type_node = cil.TypeNode(name, line, column) + self.types_map[name] = type_node + self.dottypes.append(type_node) + return type_node + + def register_data(self, value, line, column): + vname = f'data_{len(self.dotdata)}' + data_node = cil.DataNode(vname, value, line, column) + self.dotdata.append(data_node) + return data_node + + def register_label(self, label, line, column): + lname = f'{label}_{self.current_function.labels_count}' + self.current_function.labels_count += 1 + return cil.LabelNode(lname, line, column) + + def init_name(self, name): + return f'__init_at_{name}' + + def init_attr_name(self, name): + return f'__init_attr_at_{name}' + + def register_runtime_error(self, condition, msg, line, column): + error_node = self.register_label('error_label', line, column) + continue_node = self.register_label('continue_label', line, column) + self.register_instruction(cil.GotoIfNode(condition, error_node.label, line, column)) + self.register_instruction(cil.GotoNode(continue_node.label, line, column)) + self.register_instruction(error_node) + data_node = self.register_data(msg, line, column) + self.register_instruction(cil.ErrorNode(data_node, line, column)) + self.register_instruction(continue_node) + + def register_builtin(self): + # Object + line, column = 0, 0 + type_node = self.register_type('Object', line, column) + + self.current_function = self.register_function(self.init_name('Object'), line, column) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.AllocateNode('Object', instance, line, column)) + self.register_instruction(cil.ReturnNode(line, column, instance)) + + self.current_function = self.register_function(self.to_function_name('abort', 'Object'), line, column) + self_param = self.register_param(self.vself, line, column) + vname = self.define_internal_local(line, column) + abort_data = self.register_data('Abort called from class ', line, column) + self.register_instruction(cil.LoadNode(vname, abort_data, line, column)) + self.register_instruction(cil.PrintStringNode(vname, line, column)) + self.register_instruction(cil.TypeOfNode(vname, self_param, line, column)) + self.register_instruction(cil.PrintStringNode(vname, line, column)) + self.register_instruction(cil.LoadNode(vname, self.breakline_data, line, column)) + self.register_instruction(cil.PrintStringNode(vname, line, column)) + self.register_instruction(cil.ExitNode()) + + self.current_function = self.register_function(self.to_function_name('type_name', 'Object'), line, column) + self_param = self.register_param(self.vself, line, column) + result = self.define_internal_local(line, column) + self.register_instruction(cil.TypeOfNode(result, self_param, line, column)) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.ArgNode(result, line, column)) + self.register_instruction(cil.StaticCallNode(self.init_name('String'), instance, line, column)) + self.register_instruction(cil.ReturnNode(line, column, instance)) + + self.current_function = self.register_function(self.to_function_name('copy', 'Object'), line, column) + self_param = self.register_param(self.vself, line, column) + result = self.define_internal_local(line, column) + self.register_instruction(cil.CopyNode(result, self_param, line, column)) + self.register_instruction(cil.ReturnNode(line, column, result)) + + type_node.methods = {name: self.to_function_name(name, 'Object') for name in ['abort', 'type_name', 'copy']} + type_node.methods['init'] = self.init_name('Object') + obj_methods = ['abort', 'type_name', 'copy'] + + # IO + type_node = self.register_type('IO', line, column) + + self.current_function = self.register_function(self.init_name('IO'), line, column) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.AllocateNode('IO', instance, line, column)) + self.register_instruction(cil.ReturnNode(line, column, instance)) + + self.current_function = self.register_function(self.to_function_name('out_string', 'IO'), line, column) + self_param = self.register_param(self.vself, line, column) + x = self.register_param(VariableInfo('x', None), line, column) + vname = self.define_internal_local(line, column) + self.register_instruction(cil.GetAttribNode(vname, x, 'value', 'String', line, column)) + self.register_instruction(cil.PrintStringNode(vname, line, column)) + self.register_instruction(cil.ReturnNode(line, column, self_param)) + + self.current_function = self.register_function(self.to_function_name('out_int', 'IO'), line, column) + self_param = self.register_param(self.vself, line, column) + x = self.register_param(VariableInfo('x', None), line, column) + vname = self.define_internal_local(line, column) + self.register_instruction(cil.GetAttribNode(vname, x, 'value', 'Int', line, column)) + self.register_instruction(cil.PrintIntNode(vname, line, column)) + self.register_instruction(cil.ReturnNode(line, column, self_param)) + + self.current_function = self.register_function(self.to_function_name('in_string', 'IO'), line, column) + self_param = self.register_param(self.vself, line, column) + result = self.define_internal_local(line, column) + self.register_instruction(cil.ReadStringNode(result, line, column)) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.ArgNode(result, line, column)) + self.register_instruction(cil.StaticCallNode(self.init_name('String'), instance, line, column)) + self.register_instruction(cil.ReturnNode(line, column, value=instance)) + + self.current_function = self.register_function(self.to_function_name('in_int', 'IO'), line, column) + self_param = self.register_param(self.vself, line, column) + result = self.define_internal_local(line, column) + self.register_instruction(cil.ReadIntNode(result, line, column)) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.ArgNode(result, line, column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Int'), instance, line, column)) + self.register_instruction(cil.ReturnNode(line, column, instance)) + + type_node.methods = {method: self.to_function_name(method, 'Object') for method in obj_methods} + type_node.methods.update({name: self.to_function_name(name, 'IO') for name in + ['out_string', 'out_int', 'in_string', 'in_int']}) + type_node.methods['init'] = self.init_name('IO') + + # String + type_node = self.register_type('String', line, column) + type_node.attributes = {name:self.register_attribute(name, 'String', 0, 0) for name in ['value', 'length']} + + self.current_function = self.register_function(self.init_name('String'), line, column) + val = self.register_param(VariableInfo('val', None), line, column) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.AllocateNode('String', instance, line, column)) + self.register_instruction(cil.SetAttribNode(instance, 'value', val, 'String', line, column)) + result = self.define_internal_local(line, column) + self.register_instruction(cil.LengthNode(result, val, line, column)) + attr = self.define_internal_local(line, column) + self.register_instruction(cil.ArgNode(result, line, column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Int'), attr, line, column)) + self.register_instruction(cil.SetAttribNode(instance, 'length', attr, 'String', line, column)) + self.register_instruction(cil.ReturnNode(line, column, instance)) + + self.current_function = self.register_function(self.to_function_name('length', 'String'), line, column) + self_param = self.register_param(self.vself, line, column) + result = self.define_internal_local(line, column) + self.register_instruction(cil.GetAttribNode(result, self_param, 'length', 'String', line, column)) + self.register_instruction(cil.ReturnNode(line, column, result)) + + self.current_function = self.register_function(self.to_function_name('concat', 'String'), line, column) + self_param = self.register_param(self.vself, line, column) + s = self.register_param(VariableInfo('s', None), line, column) + str_1 = self.define_internal_local(line, column) + str_2 = self.define_internal_local(line, column) + length_1 = self.define_internal_local(line, column) + length_2 = self.define_internal_local(line, column) + self.register_instruction(cil.GetAttribNode(str_1, self_param, 'value', 'String', line, column)) + self.register_instruction(cil.GetAttribNode(str_2, s, 'value', 'String', line, column)) + self.register_instruction(cil.GetAttribNode(length_1, self_param, 'length', 'String', line, column)) + self.register_instruction(cil.GetAttribNode(length_2, s, 'length', 'String', line, column)) + self.register_instruction(cil.GetAttribNode(length_1, length_1, 'value', 'Int', line, column)) + self.register_instruction(cil.GetAttribNode(length_2, length_2, 'value', 'Int', line, column)) + self.register_instruction(cil.PlusNode(length_1, length_1, length_2, line, column)) + + result = self.define_internal_local(line, column) + self.register_instruction(cil.ConcatNode(result, str_1, str_2, length_1, line, column)) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.ArgNode(result, line, column)) + self.register_instruction(cil.StaticCallNode(self.init_name('String'), instance, line, column)) + self.register_instruction(cil.ReturnNode(line, column, instance)) + + self.current_function = self.register_function(self.to_function_name('substr', 'String'), line, column) + self_param = self.register_param(self.vself, line, column) + i = self.register_param(VariableInfo('i', None), line, column) + l = self.register_param(VariableInfo('l', None), line, column) + result = self.define_internal_local(line, column) + index_value = self.define_internal_local(line, column) + length_value = self.define_internal_local(line, column) + length_wrapper = self.define_internal_local(line, column) + length_attr = self.define_internal_local(line, column) + length_substr = self.define_internal_local(line, column) + less_value = self.define_internal_local(line, column) + str_value = self.define_internal_local(line, column) + self.register_instruction(cil.GetAttribNode(str_value, self_param, 'value', 'String', line, column)) + self.register_instruction(cil.GetAttribNode(index_value, i, 'value', 'Int', line, column)) + self.register_instruction(cil.GetAttribNode(length_value, l, 'value', 'Int', line, column)) + # Check Out of range error + self.register_instruction(cil.GetAttribNode(length_wrapper, self_param, 'length', 'String', line, column)) + self.register_instruction(cil.GetAttribNode(length_attr, length_wrapper, 'value', 'Int', line, column)) + self.register_instruction(cil.PlusNode(length_substr, length_value, index_value, line, column)) + self.register_instruction(cil.LessNode(less_value, length_attr, length_substr, line, column)) + self.register_runtime_error(less_value, 'Substring out of range', line, column) + self.register_instruction(cil.SubstringNode(result, str_value, index_value, length_value, line, column)) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.ArgNode(result, line, column)) + self.register_instruction(cil.StaticCallNode(self.init_name('String'), instance, line, column)) + self.register_instruction(cil.ReturnNode(line, column, instance)) + + type_node.methods = {method: self.to_function_name(method, 'Object') for method in obj_methods} + type_node.methods.update({name: self.to_function_name(name, 'String') for name in ['length', 'concat', 'substr']}) + type_node.methods['init'] = self.init_name('String') + + # Int + type_node = self.register_type('Int', line, column) + type_node.attributes = {name:self.register_attribute(name, 'Int', 0, 0) for name in ['value']} + + self.current_function = self.register_function(self.init_name('Int'), line, column) + val = self.register_param(VariableInfo('val', None), line, column) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.AllocateNode('Int', instance, line, column)) + self.register_instruction(cil.SetAttribNode(instance, 'value', val, 'Int', line, column)) + self.register_instruction(cil.ReturnNode(line, column, instance)) + + type_node.methods = {method:self.to_function_name(method, 'Object') for method in obj_methods} + type_node.methods['init'] = self.init_name('Int') + + # Bool + type_node = self.register_type('Bool', line, column) + type_node.attributes = {name:self.register_attribute(name, 'Bool', 0, 0) for name in ['value']} + + self.current_function = self.register_function(self.init_name('Bool'), line, column) + val = self.register_param(VariableInfo('val', None), line, column) + instance = self.define_internal_local(line, column) + self.register_instruction(cil.AllocateNode('Bool', instance, line, column)) + self.register_instruction(cil.SetAttribNode(instance, 'value', val, 'Bool', line, column)) + self.register_instruction(cil.ReturnNode(line, column, instance)) + + type_node.methods = {method: self.to_function_name(method, 'Object') for method in obj_methods} + type_node.methods['init'] = self.init_name('Bool') + + diff --git a/src/core/cil/CilAst.py b/src/core/cil/CilAst.py new file mode 100644 index 000000000..ae3b85863 --- /dev/null +++ b/src/core/cil/CilAst.py @@ -0,0 +1,300 @@ +from ..tools import visitor + +class Node: + def __init__(self, line=0, column=0): + self.line = line + self.column = column + + +class ProgramNode(Node): + def __init__(self, dottypes, dotdata, dotcode, line, column): + super().__init__(line,column) + self.dottypes = dottypes + self.dotdata = dotdata + self.dotcode = dotcode + +class TypeNode(Node): + def __init__(self, name, line, column): + super().__init__(line, column) + self.name = name + self.attributes = {} + self.methods = {} + +class DataNode(Node): + def __init__(self, vname, value, line, column): + super().__init__(line, column) + self.name = vname + self.value = value + +class FunctionNode(Node): + def __init__(self, fname, params, localvars, instructions, line, column): + super().__init__(line, column) + self.name = fname + self.params = params + self.localvars = localvars + self.instructions = instructions + self.labels_count = 0 + +class LocalNode(Node): + def __init__(self, name, line, column): + super().__init__(line, column) + self.name = name + +class InstructionNode(Node): + pass + +class AssignNode(InstructionNode): + def __init__(self, dest, source, line, column): + super().__init__(line, column) + self.dest = dest + self.source = source + +class ArithmeticNode(InstructionNode): + def __init__(self, dest, left, right, line, column): + super().__init__(line, column) + self.dest = dest + self.left = left + self.right = right + +class PlusNode(ArithmeticNode): + pass + +class MinusNode(ArithmeticNode): + pass + +class StarNode(ArithmeticNode): + pass + +class DivNode(ArithmeticNode): + pass + +class LessNode(ArithmeticNode): + pass + +class EqualNode(ArithmeticNode): + pass + +class EqualStringNode(ArithmeticNode): + pass + +class LessEqualNode(ArithmeticNode): + pass + +class GetAttribNode(InstructionNode): + def __init__(self, dest, obj, attr, computed_type, line, column): + super().__init__(line, column) + self.dest = dest + self.obj = obj + self.attr = attr + self.computed_type = computed_type + + def __repr__(self): + return f"{self.dest} = GETATTR {self.obj} {self.attr}" + + +class SetAttribNode(InstructionNode): + def __init__(self, obj, attr, value, computed_type, line, column): + super().__init__(line, column) + self.obj = obj + self.attr = attr + self.value = value + self.computed_type = computed_type + +class AllocateNode(InstructionNode): + def __init__(self, itype, dest, line, column): + super().__init__(line, column) + self.type = itype + self.dest = dest + +class TypeOfNode(InstructionNode): + def __init__(self, dest, obj, line, column): + super().__init__(line, column) + self.obj = obj + self.dest = dest + + def __repr__(self): + return f"{self.dest} = TYPEOF {self.obj}" + + +class LabelNode(InstructionNode): + def __init__(self, label, line, column): + super().__init__(line, column) + self.label = label + + def __repr__(self): + return f"LABEL {self.label}:" + + +class GotoNode(InstructionNode): + def __init__(self, label, line, column): + super().__init__(line, column) + self.label = label + + def __repr__(self): + return f"GOTO {self.label}" + + +class GotoIfNode(InstructionNode): + def __init__(self, condition, label, line, column): + super().__init__(line, column) + self.condition = condition + self.label = label + + def __repr__(self): + return f"GOTO {self.label} if {self.condition}" + +class StaticCallNode(InstructionNode): + def __init__(self, function, dest, line, column): + super().__init__(line, column) + self.function = function + self.dest = dest + + def __repr__(self): + return f"{self.dest} = CALL {self.function}" + +class DynamicCallNode(InstructionNode): + def __init__(self, type, method, dest, line, column): + super().__init__(line, column) + self.type = type + self.method = method + self.dest = dest + + def __repr__(self): + return f"{self.dest} = VCALL {self.type} {self.method}" + +class ArgNode(InstructionNode): + def __init__(self, name, line, column): + super().__init__(line, column) + self.name = name + + def __repr__(self): + return f"ARG {self.name}" + + +class ReturnNode(InstructionNode): + def __init__(self, line, column, value=None): + super().__init__(line, column) + self.value = value + + def __repr__(self): + return f"RETURN {self.value}" + + +class LoadNode(InstructionNode): + def __init__(self, dest, msg, line, column): + super().__init__(line, column) + self.dest = dest + self.msg = msg + + def __repr__(self): + return f"{self.dest} LOAD {self.msg}" + +class LengthNode(InstructionNode): + def __init__(self, dest, source, line, column): + super().__init__(line, column) + self.dest = dest + self.source = source + +class ConcatNode(InstructionNode): + def __init__(self, dest, prefix, suffix, length, line, column): + super().__init__(line, column) + self.dest = dest + self.prefix = prefix + self.suffix = suffix + self.length = length + +class PrefixNode(InstructionNode): + pass + +class SubstringNode(InstructionNode): + def __init__(self, dest, str_value, index, length, line, column): + super().__init__(line, column) + self.dest = dest + self.str_value = str_value + self.index = index + self.length = length + +class ToStrNode(InstructionNode): + def __init__(self, dest, value, line, column): + super().__init__(line, column) + self.dest = dest + self.value = value + +class ReadStringNode(InstructionNode): + def __init__(self, dest, line, column): + super().__init__(line, column) + self.dest = dest + +class ReadIntNode(InstructionNode): + def __init__(self, dest, line, column): + super().__init__(line, column) + self.dest = dest + +class PrintStringNode(InstructionNode): + def __init__(self, str_addr, line, column): + super().__init__(line, column) + self.str_addr = str_addr + +class PrintIntNode(InstructionNode): + def __init__(self, value, line, column): + super().__init__(line, column) + self.value = value + +class ExitNode(InstructionNode): + pass + +class CopyNode(InstructionNode): + def __init__(self, dest, value, line, column): + super().__init__(line, column) + self.dest = dest + self.value = value + +class ErrorNode(InstructionNode): + def __init__(self, data, line, column): + super().__init__(line, column) + self.data = data + +class VoidNode(InstructionNode): + def __str__(self): + return 'VOID' + +class NameNode(InstructionNode): + def __init__(self, dest, value, line, column): + super().__init__(line, column) + self.dest = dest + self.value = value + +class NotNode(InstructionNode): + def __init__(self, dest, value, line, column): + super().__init__(line, column) + self.dest = dest + self.value = value + +class ComplementNode(InstructionNode): + def __init__(self, dest, value, line, column): + super().__init__(line, column) + self.dest = dest + self.value = value + +class VarNode(InstructionNode): + def __init__(self, name, line, column): + super().__init__(line, column) + self.name = name + + def __str__(self): + return f'{self.name}' + +class AttributeNode(VarNode): + def __init__(self, name, type, line, column): + super().__init__(name, line, column) + self.type = type + + def __str__(self): + return f'{self.type}.{self.name}' + +class ParamNode(VarNode): + def __init__(self, name, line, column): + super().__init__(name, line, column) + + def __str__(self): + return f'PARAM {self.name}' \ No newline at end of file diff --git a/src/core/cil/CilAstFormatter.py b/src/core/cil/CilAstFormatter.py new file mode 100644 index 000000000..96bfbe31b --- /dev/null +++ b/src/core/cil/CilAstFormatter.py @@ -0,0 +1,206 @@ +from ..tools import visitor +from .CilAst import * + + +class PrintVisitor(object): + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node): + dottypes = '\n'.join(self.visit(t) for t in node.dottypes) + dotdata = '\n'.join(self.visit(t) for t in node.dotdata) + dotcode = '\n'.join(self.visit(t) for t in node.dotcode) + + return f'.TYPES\n{dottypes}\n\n.DATA\n{dotdata}\n\n.CODE\n{dotcode}' + + @visitor.when(TypeNode) + def visit(self, node): + attributes = '\n\t'.join(f'attribute {x}' for x in node.attributes) + methods = '\n\t'.join(f'method {x}' for x in node.methods) + + return f'type {node.name} {{\n\t{attributes}\n\t{methods}\n}}' + + @visitor.when(DataNode) + def visit(self, node): + return f'DATA "{node.value}"' + + @visitor.when(FunctionNode) + def visit(self, node): + params = '\n\t'.join(self.visit(x) for x in node.params) + localvars = '\n\t'.join(self.visit(x) for x in node.localvars) + instructions = '\n\t'.join(self.visit(x) for x in node.instructions) + + return f'function {node.name} {{\n\t{params}\n\t{localvars}\n\n\t{instructions}\n}}' + + @visitor.when(ParamNode) + def visit(self, node): + return f'PARAM {node.name}' + + @visitor.when(LocalNode) + def visit(self, node): + return f'LOCAL {node.name}' + + @visitor.when(AssignNode) + def visit(self, node): + return f'{node.dest} = {node.source}' + + @visitor.when(PlusNode) + def visit(self, node): + return f'{node.dest} = {node.left} + {node.right}' + + @visitor.when(MinusNode) + def visit(self, node): + return f'{node.dest} = {node.left} - {node.right}' + + @visitor.when(StarNode) + def visit(self, node): + return f'{node.dest} = {node.left} * {node.right}' + + @visitor.when(DivNode) + def visit(self, node): + return f'{node.dest} = {node.left} / {node.right}' + + @visitor.when(AllocateNode) + def visit(self, node): + return f'{node.dest} = ALLOCATE {node.type}' + + @visitor.when(TypeOfNode) + def visit(self, node): + return f'{node.dest} = TYPEOF {node.obj}' + + @visitor.when(StaticCallNode) + def visit(self, node): + return f'{node.dest} = CALL {node.function}' + + @visitor.when(DynamicCallNode) + def visit(self, node): + return f'{node.dest} = VCALL {node.type} {node.method}' + + @visitor.when(ArgNode) + def visit(self, node): + return f'ARG {node.name}' + + @visitor.when(ReturnNode) + def visit(self, node): + return f'RETURN {node.value if node.value is not None else ""}' + + @visitor.when(LoadNode) + def visit(self, node): + return f'{node.dest} = LOAD {self.visit(node.msg)}' + + @visitor.when(PrintStringNode) + def visit(self, node: PrintStringNode): + return f'PRINTSTRING {node.str_addr}' + + @visitor.when(PrintIntNode) + def visit(self, node: PrintIntNode): + return f'PRINTINT {node.value}' + + @visitor.when(ExitNode) + def visit(self, node: ExitNode): + return f'EXIT' + + @visitor.when(CopyNode) + def visit(self, node): + return f'{node.dest} = COPY {node.value}' + + @visitor.when(GetAttribNode) + def visit(self, node: GetAttribNode): + return f'{node.dest} = GETATTRIB {node.obj}.{node.attr} {node.computed_type}' + + @visitor.when(ErrorNode) + def visit(self, node: ErrorNode): + return f'ERROR {self.visit(node.data)}' + + @visitor.when(ReadStringNode) + def visit(self, node: ReadStringNode): + return f'{node.dest} = READ' + + @visitor.when(ReadIntNode) + def visit(self, node: ReadIntNode): + return f'{node.dest} = READ' + + @visitor.when(SetAttribNode) + def visit(self, node: SetAttribNode): + return f'SETATTR {node.obj}.{node.attr}: {node.computed_type} = {node.value}' + + @visitor.when(LessNode) + def visit(self, node: LessNode): + return f'{node.dest} = {node.left} < {node.right}' + + @visitor.when(GotoIfNode) + def visit(self, node: GotoIfNode): + return f'GOTOIF {node.condition} {node.label}' + + @visitor.when(GotoNode) + def visit(self, node: GotoNode): + return f'GOTO {node.label}' + + @visitor.when(LabelNode) + def visit(self, node: LabelNode): + return f'LABEL {node.label}' + + @visitor.when(SubstringNode) + def visit(self, node: SubstringNode): + return f'{node.dest} = SUBSTRING {node.str_value}[{node.index}:{node.index} up to {node.length}]' + + @visitor.when(ConcatNode) + def visit(self, node: ConcatNode): + return f'{node.dest} = CONCAT {node.prefix} + {node.suffix}' + + @visitor.when(LengthNode) + def visit(self, node: LengthNode): + return f'{node.dest} = LENGTH {node.source}' + + @visitor.when(EqualNode) + def visit(self, node: EqualNode): + return f'{node.dest} = {node.left} == {node.right}' + + @visitor.when(NameNode) + def visit(self, node: NameNode): + return f'{node.dest} = NAME {node.value}' + + @visitor.when(EqualStringNode) + def visit(self, node: EqualStringNode): + return f'{node.dest} = {node.left} == {node.right}' + + @visitor.when(ComplementNode) + def visit(self, node: ComplementNode): + return f'{node.dest} = ~{node.value}' + + @visitor.when(LessEqualNode) + def visit(self, node: LessEqualNode): + return f'{node.dest} = {node.left} <= {node.right}' + + @visitor.when(PrefixNode) + def visit(self, node: PrefixNode): + return f'PREFFIXNODE' + + @visitor.when(ToStrNode) + def visit(self, node: ToStrNode): + return f'{node.dest} = str({node.value})' + + @visitor.when(VoidNode) + def visit(self, node: VoidNode): + return 'VOID' + + @visitor.when(NotNode) + def visit(self, node: NotNode): + return f'{node.dest} = NOT {node.value}' + + @visitor.when(VarNode) + def visit(self, node: VarNode): + return f'{node.name}' + + @visitor.when(AttributeNode) + def visit(self, node: AttributeNode): + return f'ATTRIBUTE {node.type}.{node.name}' + + @visitor.when(ParamNode) + def visit(self, node: ParamNode): + return f'{node.name}' + + +printer = PrintVisitor() diff --git a/src/core/cil/CoolToCilVisitor.py b/src/core/cil/CoolToCilVisitor.py new file mode 100644 index 000000000..1078be34e --- /dev/null +++ b/src/core/cil/CoolToCilVisitor.py @@ -0,0 +1,663 @@ +import core.cool.CoolAst as cool +from .BaseCoolToCilVisitor import * +from core.tools import visitor +from ..tools.Semantic import Scope + + +class COOLToCILVisitor(BaseCOOLToCILVisitor): + def __init__(self, context): + super().__init__(context) + + def collect_types(self, node): + self.types_map[node.id.lex] = type = self.register_type(node.id.lex, node.id.line, node.id.column) + # Guardar métodos de las clases padres + iter_type = self.context.get_type(node.id.lex) + + generation = [] + while iter_type is not None: + generation.append(iter_type) + iter_type = iter_type.parent + + generation.reverse() + for i in generation: + methods = sorted(i.methods) + attributes = sorted(i.attributes) + for meth in methods: + type.methods[meth] = self.to_function_name(meth, i.name) + for attr in attributes: + type.attributes[attr.name] = cil.AttributeNode(attr.name, i.name, i.line, i.column) + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(cool.ProgramNode) + def visit(self, node: cool.ProgramNode, scope: Scope): + self.current_function = self.register_function('entry', line=0, column=0) + main_instance = self.define_internal_local(line=0, column=0) + self.register_instruction(cil.StaticCallNode(self.init_name('Main'), main_instance, line=0, column=0)) + self.register_instruction(cil.ArgNode(main_instance, line=0, column=0)) + self.register_instruction(cil.StaticCallNode(self.to_function_name('main', 'Main'), + main_instance, line=0, column=0)) + self.register_instruction(cil.ReturnNode(line=0, column=0, value=0)) + + self.register_builtin() + self.current_function = None + for x in node.declarations: + self.collect_types(x) + + for x, y in zip(node.declarations, scope.childs): + self.visit(x, y) + + return cil.ProgramNode(self.dottypes, self.dotdata, self.dotcode, line=0, column=0) + + @visitor.when(cool.ClassDeclarationNode) + def visit(self, node: cool.ClassDeclarationNode, scope: Scope): + self.current_type = self.context.get_type(node.id.lex) + type = self.types_map[node.id.lex] + + self.current_function = self.register_function(self.init_attr_name(node.id.lex), + line=node.line, column=node.column) + type.methods['__init_attr'] = self.current_function.name + self_param = self.register_param(self.vself, line=node.line, column=node.column) + self.localvars.extend(type.attributes.values()) + self.var_names.update({i:cil.AttributeNode(j.name, type.name, node.line, node.column) + for i,j in type.attributes.items()}) + + self.vself.name = self_param + # Inicializando los atributos de la clase y llamando al constructor del padre + if self.current_type.parent.name not in ('Object', 'IO'): + variable = self.define_internal_local(line=node.line, column=node.column) + self.register_instruction(cil.ArgNode(self_param, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode( + self.init_attr_name(self.current_type.parent.name), variable, line=node.line, column=node.column)) + + # Inicializando los atributos de la clase + for feat, child in zip(node.features, scope.childs): + if isinstance(feat, cool.AttrDeclarationNode): + self.visit(feat, child) + self.register_instruction(cil.SetAttribNode(self_param, feat.id.lex, feat.ret_expr, node.id.lex, + line=node.line, column=node.column)) + self.register_instruction(cil.ReturnNode(node.line, node.column, self_param)) + + # TypeNode de la clase + # type = self.types_map[node.id.lex] + # type.attributes = [i.name for i in self.current_type.attributes] + + # Visitar funciones dentro de la clase + for feat, child in zip(node.features, scope.childs): + if isinstance(feat, cool.FuncDeclarationNode): + self.visit(feat, child) + self.vself.name = 'self' + + # Allocate de la clase + self.current_function = self.register_function(self.init_name(node.id.lex), line=node.line, column=node.column) + type.methods['__init'] = self.current_function.name + self.localvars.extend(type.attributes.values()) + instance = self.define_internal_local(line=node.line, column=node.column) + self.register_instruction(cil.AllocateNode(node.id.lex, instance, line=node.line, column=node.column)) + + variable = self.define_internal_local(line=node.line, column=node.column) + self.register_instruction(cil.ArgNode(instance, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_attr_name(node.id.lex), variable, + line=node.line, column=node.column)) + + self.register_instruction(cil.ReturnNode(value=variable, line=node.line, column=node.column)) + + self.current_function = None + self.current_type = None + + @visitor.when(cool.AttrDeclarationNode) + def visit(self, node: cool.AttrDeclarationNode, scope: Scope): + variable = self.define_internal_local(line=node.line, column=node.column) + if node.expression: + self.visit(node.expression, scope.childs[0]) + self.register_instruction(cil.AssignNode(variable, node.expression.ret_expr, + line=node.expression.line, column=node.expression.column)) + elif node.type.lex in self.value_types: + if node.type.lex == 'SELF_TYPE': + stype = self.current_type.name + else: + stype = node.type.lex + + if stype == 'Int': + self.register_instruction(cil.ArgNode(0, line=node.type.line, column=node.type.column)) + elif stype == 'Bool': + self.register_instruction(cil.ArgNode(0, line=node.type.line, column=node.type.column)) + elif stype == 'String': + data = self.emptystring_data + self.register_instruction(cil.LoadNode(variable, data, line=node.line, column=node.column)) + self.register_instruction(cil.ArgNode(variable, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name(stype), variable, + line=node.line, column=node.column)) + node.ret_expr = variable + + @visitor.when(cool.FuncDeclarationNode) + def visit(self, node: cool.FuncDeclarationNode, scope: Scope): + self.current_method = self.current_type.get_method(node.id.lex) + type = self.types_map[self.current_type.name] + self.current_function = self.register_function(self.to_function_name(self.current_method.name, + self.current_type.name), + line=node.line, column=node.column) + self.localvars.extend(type.attributes.values()) + + self_param = self.register_param(self.vself, line=node.line, column=node.column) + self.vself.name = self_param + for param, type in node.params: + self.register_param(VariableInfo(param.lex, type.lex), line=param.line, column=param.column) + + self.visit(node.body, scope.childs[0]) + self.register_instruction(cil.ReturnNode(value=node.body.ret_expr, + line=node.body.line, column=node.body.column)) + self.current_method = None + self.vself.name = 'self' + + @visitor.when(cool.IfThenElseNode) + def visit(self, node: cool.IfThenElseNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + condition = self.define_internal_local(line=node.line, column=node.column) + + then_label = self.register_label('then_label', line=node.line, column=node.column) + continue_label = self.register_label('continue_label', line=node.line, column=node.column) + + # IF + self.visit(node.condition, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(condition, node.condition.ret_expr, 'value', 'Bool', + line=node.condition.line, column=node.condition.column)) + self.register_instruction(cil.GotoIfNode(condition, then_label.label, line=node.condition.line, + column=node.condition.column)) + + # ELSE + self.visit(node.else_body, scope.childs[2]) + self.register_instruction(cil.AssignNode(ret, node.else_body.ret_expr, line=node.else_body.line, + column=node.else_body.column)) + self.register_instruction(cil.GotoNode(continue_label.label, line=node.else_body.line, + column=node.else_body.column)) + + # THEN + self.register_instruction(then_label) + self.visit(node.if_body, scope.childs[1]) + self.register_instruction(cil.AssignNode(ret, node.if_body.ret_expr, line=node.if_body.line, + column=node.if_body.column)) + + self.register_instruction(continue_label) + node.ret_expr = ret + + @visitor.when(cool.WhileLoopNode) + def visit(self, node: cool.WhileLoopNode, scope: Scope): + while_label = self.register_label('while_label', line=node.line, column=node.column) + loop_label = self.register_label('loop_label', line=node.line, column=node.column) + pool_label = self.register_label('pool_label', line=node.line, column=node.column) + condition = self.define_internal_local(line=node.line, column=node.column) + + self.register_instruction(while_label) + self.visit(node.condition, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(condition, node.condition.ret_expr, 'value', 'Bool', + line=node.condition.line, column=node.condition.column)) + self.register_instruction(cil.GotoIfNode(condition, loop_label.label, line=node.condition.line, + column=node.condition.column)) + self.register_instruction(cil.GotoNode(pool_label.label, line=node.condition.line, + column=node.condition.column)) + + self.register_instruction(loop_label) + self.visit(node.body, scope.childs[1]) + self.register_instruction( + cil.GotoNode(while_label.label, line=node.condition.line, column=node.condition.column)) + + self.register_instruction(pool_label) + node.ret_expr = cil.VoidNode() + + @visitor.when(cool.BlockNode) + def visit(self, node: cool.BlockNode, scope: Scope): + for expr, child in zip(node.expressions, scope.childs): + self.visit(expr, child) + node.ret_expr = node.expressions[-1].ret_expr + + @visitor.when(cool.LetInNode) + def visit(self, node: cool.LetInNode, scope: Scope): + for (id, type, expr), child in zip(node.let_body, scope.childs[:-1]): + variable = self.register_local(VariableInfo(id.lex, type.lex), line=id.line, column=id.column) + if expr: + self.visit(expr, child) + self.register_instruction(cil.AssignNode(variable, expr.ret_expr, line=expr.line, column=expr.column)) + elif type.lex in self.value_types: + if type.lex == 'SELF_TYPE': + stype = self.current_type.name + else: + stype = type.lex + if stype == 'Int': + self.register_instruction(cil.ArgNode(0, line=node.line, column=node.column)) + elif stype == 'Bool': + self.register_instruction(cil.ArgNode(0, line=node.line, column=node.column)) + elif stype == 'String': + data = self.emptystring_data + self.register_instruction(cil.LoadNode(variable, data, line=node.line, column=node.column)) + self.register_instruction(cil.ArgNode(variable, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name(stype), variable, + line=node.line, column=node.column)) + + self.visit(node.in_body, scope.childs[-1]) + node.ret_expr = node.in_body.ret_expr + + @visitor.when(cool.CaseOfNode) + def visit(self, node: cool.CaseOfNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + vtype = self.define_internal_local(line=node.line, column=node.column) + cond = self.define_internal_local(line=node.line, column=node.column) + + self.visit(node.expression, scope.childs[0]) + self.register_instruction(cil.TypeOfNode(vtype, node.expression.ret_expr, line=node.expression.line, + column=node.expression.column)) + + isvoid = self.define_internal_local(line=node.line, column=node.column) + self.register_instruction(cil.EqualNode(isvoid, node.expression.ret_expr, cil.VoidNode(), + line=node.expression.line, column=node.expression.column)) + self.register_runtime_error(isvoid, f'{node.expression.line, node.expression.column} - ' + f'RuntimeError: Case on void', + line=node.expression.line, column=node.expression.column) + + end_label = self.register_label('case_end_label', line=node.line, column=node.column) + + branch_type = self.define_internal_local(line=node.line, column=node.column) + seen = [] + labels = [] + branches = sorted(node.branches, key=lambda x: self.context.get_type(x[1].lex).depth, reverse=True) + for p, (id, type, expr) in enumerate(branches): + labels.append(self.register_label(f'case_label_{p}', line=id.line, column=id.column)) + + for t in self.context.subtree(type.lex): + if t not in seen: + seen.append(t) + self.register_instruction(cil.NameNode(branch_type, t.name, line=id.line, column=id.column)) + self.register_instruction(cil.EqualNode(cond, branch_type, vtype, line=id.line, column=id.column)) + self.register_instruction(cil.GotoIfNode(cond, labels[-1].label, line=id.line, column=id.column)) + + data = self.register_data(f'{node.expression.line, node.expression.column} - ' + f'RuntimeError: Case statement without a match branch', + line=node.expression.line, column=node.expression.column) + self.register_instruction(cil.ErrorNode(data, line=node.expression.line, column=node.expression.column)) + + for p, label in enumerate(labels): + id, type, expr = branches[p] + sc = scope.childs[p + 1] + + self.register_instruction(label) + var = self.register_local(VariableInfo(id.lex, vtype), line=id.line, column=id.column) + self.register_instruction(cil.AssignNode(var, node.expression.ret_expr, line=node.expression.line, + column=node.expression.column)) + self.visit(expr, sc) + self.register_instruction(cil.AssignNode(ret, expr.ret_expr, line=expr.line, column=expr.column)) + self.register_instruction(cil.GotoNode(end_label.label, line=expr.line, column=expr.column)) + + self.register_instruction(end_label) + node.ret_expr = ret + + @visitor.when(cool.AssignNode) + def visit(self, node: cool.AssignNode, scope: Scope): + var = self.var_names[node.id.lex] + self.visit(node.expression, scope.childs[0]) + self.register_instruction(cil.AssignNode(var, node.expression.ret_expr, + line=node.expression.line, column=node.expression.column)) + node.ret_expr = var + + @visitor.when(cool.NotNode) + def visit(self, node: cool.NotNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + value = self.define_internal_local(line=node.line, column=node.column) + neg_value = self.define_internal_local(line=node.line, column=node.column) + + self.visit(node.expression, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(value, node.expression.ret_expr, 'value', 'Bool', + line=node.expression.line, column=node.expression.column)) + self.register_instruction(cil.NotNode(neg_value, value, + line=node.expression.line, column=node.expression.column)) + self.register_instruction(cil.ArgNode(neg_value, line=node.expression.line, column=node.expression.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Bool'), ret, line=node.line, column=node.column)) + + node.ret_expr = ret + + @visitor.when(cool.LessEqualNode) + def visit(self, node: cool.LessEqualNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + left = self.define_internal_local(line=node.line, column=node.column) + right = self.define_internal_local(line=node.line, column=node.column) + value = self.define_internal_local(line=node.line, column=node.column) + + self.visit(node.left, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(left, node.left.ret_expr, 'value', 'Int', + line=node.left.line, column=node.left.line)) + self.visit(node.right, scope.childs[1]) + self.register_instruction(cil.GetAttribNode(right, node.right.ret_expr, 'value', 'Int', + line=node.right.line, column=node.right.line)) + self.register_instruction(cil.LessEqualNode(value, left, right, line=node.line, column=node.column)) + + self.register_instruction(cil.ArgNode(value, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Bool'), ret, line=node.line, column=node.column)) + + node.ret_expr = ret + + @visitor.when(cool.LessNode) + def visit(self, node: cool.LessNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + left = self.define_internal_local(line=node.line, column=node.column) + right = self.define_internal_local(line=node.line, column=node.column) + value = self.define_internal_local(line=node.line, column=node.column) + + self.visit(node.left, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(left, node.left.ret_expr, 'value', 'Int', + line=node.left.line, column=node.left.line)) + self.visit(node.right, scope.childs[1]) + self.register_instruction(cil.GetAttribNode(right, node.right.ret_expr, 'value', 'Int', + line=node.right.line, column=node.right.line)) + self.register_instruction(cil.LessNode(value, left, right, line=node.line, column=node.column)) + + self.register_instruction(cil.ArgNode(value, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Bool'), ret, line=node.line, column=node.column)) + + node.ret_expr = ret + + @visitor.when(cool.PlusNode) + def visit(self, node: cool.PlusNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + left = self.define_internal_local(line=node.line, column=node.column) + right = self.define_internal_local(line=node.line, column=node.column) + value = self.define_internal_local(line=node.line, column=node.column) + + self.visit(node.left, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(left, node.left.ret_expr, 'value', 'Int', + line=node.left.line, column=node.left.line)) + self.visit(node.right, scope.childs[1]) + self.register_instruction(cil.GetAttribNode(right, node.right.ret_expr, 'value', 'Int', + line=node.right.line, column=node.right.line)) + self.register_instruction(cil.PlusNode(value, left, right, line=node.line, column=node.column)) + + self.register_instruction(cil.ArgNode(value, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Int'), ret,line=node.line, column=node.column)) + + node.ret_expr = ret + + @visitor.when(cool.MinusNode) + def visit(self, node: cool.MinusNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + left = self.define_internal_local(line=node.line, column=node.column) + right = self.define_internal_local(line=node.line, column=node.column) + value = self.define_internal_local(line=node.line, column=node.column) + + self.visit(node.left, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(left, node.left.ret_expr, 'value', 'Int', + line=node.left.line, column=node.left.line)) + self.visit(node.right, scope.childs[1]) + self.register_instruction(cil.GetAttribNode(right, node.right.ret_expr, 'value', 'Int', + line=node.right.line, column=node.right.line)) + self.register_instruction(cil.MinusNode(value, left, right, line=node.line, column=node.column)) + + self.register_instruction(cil.ArgNode(value, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Int'), ret, line=node.line, column=node.column)) + + node.ret_expr = ret + + @visitor.when(cool.StarNode) + def visit(self, node: cool.StarNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + left = self.define_internal_local(line=node.line, column=node.column) + right = self.define_internal_local(line=node.line, column=node.column) + value = self.define_internal_local(line=node.line, column=node.column) + + self.visit(node.left, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(left, node.left.ret_expr, 'value', 'Int', + line=node.left.line, column=node.left.line)) + self.visit(node.right, scope.childs[1]) + self.register_instruction(cil.GetAttribNode(right, node.right.ret_expr, 'value', 'Int', + line=node.right.line, column=node.right.line)) + self.register_instruction(cil.StarNode(value, left, right, line=node.line, column=node.column)) + + self.register_instruction(cil.ArgNode(value, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Int'), ret, line=node.line, column=node.column)) + node.ret_expr = ret + + @visitor.when(cool.DivNode) + def visit(self, node: cool.DivNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + left = self.define_internal_local(line=node.line, column=node.column) + right = self.define_internal_local(line=node.line, column=node.column) + value = self.define_internal_local(line=node.line, column=node.column) + zero = self.define_internal_local(line=node.line, column=node.column) + + self.visit(node.left, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(left, node.left.ret_expr, 'value', 'Int', + line=node.left.line, column=node.left.line)) + self.visit(node.right, scope.childs[1]) + + self.register_instruction(cil.ArgNode(0, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Int'), zero, line=node.line, column=node.column)) + self.register_instruction(cil.GetAttribNode(zero, zero, 'value', 'Int', + line=node.right.line, column=node.right.line)) + self.register_instruction(cil.GetAttribNode(right, node.right.ret_expr, 'value', 'Int', + line=node.right.line, column=node.right.line)) + self.register_instruction(cil.EqualNode(zero, zero, right, + line=node.line, column=node.column)) + self.register_runtime_error(zero, f'{node.right.line, node.right.column} - ' + f'RuntimeError: Division by zero', + line=node.right.line, column=node.right.column) + + self.register_instruction(cil.DivNode(value, left, right, line=node.line, column=node.column)) + + self.register_instruction(cil.ArgNode(value, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Int'), ret, line=node.line, column=node.column)) + node.ret_expr = ret + + @visitor.when(cool.IsVoidNode) + def visit(self, node: cool.IsVoidNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + answer = self.define_internal_local(line=node.line, column=node.column) + + void = cil.VoidNode() + self.visit(node.expression, scope.childs[0]) + self.register_instruction(cil.EqualNode(answer, node.expression.ret_expr, void, + line=node.expression.line, column=node.expression.column)) + + self.register_instruction(cil.ArgNode(answer, line=node.expression.line, column=node.expression.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Bool'), ret, line=node.line, column=node.column)) + node.ret_expr = ret + + @visitor.when(cool.ComplementNode) + def visit(self, node: cool.ComplementNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + value = self.define_internal_local(line=node.line, column=node.column) + answer = self.define_internal_local(line=node.line, column=node.column) + + self.visit(node.expression, scope.childs[0]) + self.register_instruction(cil.GetAttribNode(value, node.expression.ret_expr, 'value', 'Int', + line=node.expression.line, column=node.expression.column)) + self.register_instruction(cil.ComplementNode(answer, value, + line=node.expression.line, column=node.expression.column)) + + self.register_instruction(cil.ArgNode(answer,line=node.expression.line, column=node.expression.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Int'), ret, line=node.line, column=node.column)) + node.ret_expr = ret + + @visitor.when(cool.EqualNode) + def visit(self, node: cool.EqualNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + left = self.define_internal_local(line=node.line, column=node.column) + right = self.define_internal_local(line=node.line, column=node.column) + type_left = self.define_internal_local(line=node.line, column=node.column) + type_int = self.define_internal_local(line=node.line, column=node.column) + type_string = self.define_internal_local(line=node.line, column=node.column) + type_bool = self.define_internal_local(line=node.line, column=node.column) + equal = self.define_internal_local(line=node.line, column=node.column) + value = self.define_internal_local(line=node.line, column=node.column) + + int_comparisson = self.register_label('int_comparisson', line=node.line, column=node.column) + string_comparisson = self.register_label('string_comparisson', line=node.line, column=node.column) + bool_comparisson = self.register_label('bool_comparisson', line=node.line, column=node.column) + continue_label = self.register_label('continue_label', line=node.line, column=node.column) + + self.visit(node.left, scope.childs[0]) + self.visit(node.right, scope.childs[1]) + + self.register_instruction(cil.TypeOfNode(type_left, node.left.ret_expr, + line=node.left.line, column=node.left.column)) + self.register_instruction(cil.NameNode(type_int, 'Int', line=node.line, column=node.column)) + self.register_instruction(cil.NameNode(type_string, 'String', line=node.line, column=node.column)) + self.register_instruction(cil.NameNode(type_bool, 'Bool', line=node.line, column=node.column)) + + self.register_instruction(cil.EqualNode(equal, type_left, type_int, + line=node.left.line, column=node.left.column)) + self.register_instruction(cil.GotoIfNode(equal, int_comparisson.label, + line=node.left.line, column=node.left.column)) + self.register_instruction(cil.EqualNode(equal, type_left, type_string, + line=node.left.line, column=node.left.column)) + self.register_instruction(cil.GotoIfNode(equal, string_comparisson.label, + line=node.left.line, column=node.left.column)) + self.register_instruction(cil.EqualNode(equal, type_left, type_bool, + line=node.left.line, column=node.left.column)) + self.register_instruction(cil.GotoIfNode(equal, bool_comparisson.label, + line=node.left.line, column=node.left.column)) + + self.register_instruction(cil.EqualNode(value, node.left.ret_expr, node.right.ret_expr, + line=node.left.line, column=node.left.column)) + self.register_instruction(cil.GotoNode(continue_label.label, + line=node.left.line, column=node.left.column)) + + self.register_instruction(int_comparisson) + self.register_instruction(cil.GetAttribNode(left, node.left.ret_expr, 'value', 'Int', + line=node.right.line, column=node.right.column)) + self.register_instruction(cil.GetAttribNode(right, node.right.ret_expr, 'value', 'Int', + line=node.right.line, column=node.right.column)) + self.register_instruction(cil.EqualNode(value, left, right, line=node.line, column=node.column)) + self.register_instruction(cil.GotoNode(continue_label.label, line=node.line, column=node.column)) + + self.register_instruction(string_comparisson) + self.register_instruction(cil.GetAttribNode(left, node.left.ret_expr, 'value', 'String', + line=node.left.line, column=node.left.column)) + self.register_instruction(cil.GetAttribNode(right, node.right.ret_expr, 'value', 'String', + line=node.right.line, column=node.right.column)) + self.register_instruction(cil.EqualStringNode(value, left, right, + line=node.right.line, column=node.right.column)) + self.register_instruction(cil.GotoNode(continue_label.label, line=node.line, column=node.column)) + + self.register_instruction(bool_comparisson) + self.register_instruction(cil.GetAttribNode(left, node.left.ret_expr, 'value', 'Bool', + line=node.left.line, column=node.left.column)) + self.register_instruction(cil.GetAttribNode(right, node.right.ret_expr, 'value', 'Bool', + line=node.right.line, column=node.right.column)) + self.register_instruction(cil.EqualNode(value, left, right, + line=node.right.line, column=node.right.column)) + self.register_instruction(cil.GotoNode(continue_label.label, line=node.line, column=node.column)) + + self.register_instruction(continue_label) + + self.register_instruction(cil.ArgNode(value, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Bool'), ret, line=node.line, column=node.column)) + node.ret_expr = ret + + @visitor.when(cool.FunctionCallNode) + def visit(self, node: cool.FunctionCallNode, scope: Scope): + args = [] + for arg, child in zip(node.args, scope.childs[1:]): + self.visit(arg, child) + args.append(cil.ArgNode(arg.ret_expr, line=arg.line, column=arg.column)) + + self.visit(node.obj, scope.childs[0]) + + void = cil.VoidNode() + isvoid = self.define_internal_local(line=node.line, column=node.column) + self.register_instruction(cil.EqualNode(isvoid, node.obj.ret_expr, void, + line=node.obj.line, column=node.obj.column)) + self.register_runtime_error(isvoid, f'{node.id.line, node.id.column} - RuntimeError: Dispatch on void', + line=node.id.line, column=node.id.column) + + self.register_instruction(cil.ArgNode(node.obj.ret_expr, line=node.obj.line, column=node.obj.column)) + for arg in args: self.register_instruction(arg) + ret = self.define_internal_local(line=node.line, column=node.column) + if node.type is not None: + stype = node.type.lex + self.register_instruction(cil.StaticCallNode(self.types_map[stype].methods[node.id.lex], + ret, line=node.id.line, column=node.id.column)) + else: + stype = node.obj.static_type.name + self.register_instruction(cil.ArgNode(node.obj.ret_expr, line=node.obj.line, column=node.obj.column)) + self.register_instruction(cil.DynamicCallNode(stype, self.types_map[stype].methods[node.id.lex], + ret, line=node.id.line, column=node.id.column)) + + + node.ret_expr = ret + + @visitor.when(cool.MemberCallNode) + def visit(self, node: cool.MemberCallNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + + args = [] + for arg, child in zip(node.args, scope.childs): + self.visit(arg, child) + args.append(cil.ArgNode(arg.ret_expr, line=arg.line, column=arg.column)) + + self.register_instruction(cil.ArgNode(self.vself.name, line=node.line, column=node.column)) + for arg in args: self.register_instruction(arg) + self.register_instruction(cil.ArgNode(self.vself.name, line=node.line, column=node.column)) + + stype = self.current_type.name + self.register_instruction(cil.DynamicCallNode(stype, self.types_map[stype].methods[node.id.lex], ret, line=node.id.line, column=node.id.column)) + node.ret_expr = ret + + @visitor.when(cool.NewNode) + def visit(self, node: cool.NewNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + + if node.type.lex == 'SELF_TYPE': + stype = self.current_type.name + else: + stype = node.type.lex + + if stype == 'Int': + self.register_instruction(cil.ArgNode(0, line=node.type.line, column=node.type.column)) + elif stype == 'Bool': + self.register_instruction(cil.ArgNode(0, line=node.type.line, column=node.type.column)) + elif stype == 'String': + data = self.emptystring_data + variable = self.define_internal_local(line=node.line, column=node.column) + self.register_instruction(cil.LoadNode(variable, data, line=node.line, column=node.column)) + self.register_instruction(cil.ArgNode(variable, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name(stype), ret, + line=node.type.line, column=node.type.column)) + node.ret_expr = ret + + @visitor.when(cool.IdNode) + def visit(self, node: cool.IdNode, scope: Scope): + if node.token.lex == 'self': + node.ret_expr = self.vself.name + else: + node.ret_expr = self.var_names[node.token.lex] + + @visitor.when(cool.StringNode) + def visit(self, node: cool.StringNode, scope: Scope): + try: + data = [i for i in self.dotdata if i.value == node.token.lex][0] + except IndexError: + data = self.register_data(node.token.lex, line=node.token.line, column=node.token.column) + + variable = self.define_internal_local(line=node.line, column=node.column) + ret = self.define_internal_local(line=node.line, column=node.column) + + self.register_instruction(cil.LoadNode(variable, data, line=node.line, column=node.column)) + self.register_instruction(cil.ArgNode(variable, line=node.line, column=node.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('String'), ret, + line=node.line, column=node.column)) + node.ret_expr = ret + + @visitor.when(cool.IntegerNode) + def visit(self, node: cool.IntegerNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + + self.register_instruction(cil.ArgNode(node.token.lex, line=node.token.line, column=node.token.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Int'), ret, line=node.line, column=node.column)) + node.ret_expr = ret + + @visitor.when(cool.BoolNode) + def visit(self, node: cool.BoolNode, scope: Scope): + ret = self.define_internal_local(line=node.line, column=node.column) + + self.register_instruction(cil.ArgNode(1 if node.token.lex else 0, line=node.token.line, column=node.token.column)) + self.register_instruction(cil.StaticCallNode(self.init_name('Bool'), ret, line=node.line, column=node.column)) + node.ret_expr = ret diff --git a/src/core/cool/CoolAst.py b/src/core/cool/CoolAst.py new file mode 100644 index 000000000..4efb93764 --- /dev/null +++ b/src/core/cool/CoolAst.py @@ -0,0 +1,210 @@ +# Clases necesarias para representar el AST del programa COOL +class Node: + pass + +# Raiz del AST +class ProgramNode(Node): + def __init__(self, declarations): + self.declarations = declarations + self.line = declarations[0].line + self.column = declarations[0].column + + +class DeclarationNode(Node): + pass + + +class ClassDeclarationNode(DeclarationNode): + def __init__(self, classx, idx, features, parent=None): + self.id = idx + self.parent = parent + self.features = features + self.line = classx.line + self.column = classx.column + + +class AttrDeclarationNode(DeclarationNode): + def __init__(self, idx, typex, expression=None): + self.id = idx + self.type = typex + self.expression = expression + self.line = idx.line + self.column = idx.column + + +class FuncDeclarationNode(DeclarationNode): + def __init__(self, idx, params, return_type, body): + self.id = idx + self.params = params + self.type = return_type + self.body = body + self.line = idx.line + self.column = idx.column + + +class ExpressionNode(Node): + pass + + +class IfThenElseNode(ExpressionNode): + def __init__(self, ifx, condition, if_body, else_body): + self.condition = condition + self.if_body = if_body + self.else_body = else_body + self.line = ifx.line + self.column = ifx.column + + +class WhileLoopNode(ExpressionNode): + def __init__(self, whilex, condition, body): + self.condition = condition + self.body = body + self.line = whilex.line + self.column = whilex.column + + +class BlockNode(ExpressionNode): + def __init__(self, brace, expressions): + self.expressions = expressions + self.line = brace.line + self.column = brace.column + + +class LetInNode(ExpressionNode): + def __init__(self, let, let_body, in_body): + self.let_body = let_body + self.in_body = in_body + self.line = let.line + self.column = let.column + + +class CaseOfNode(ExpressionNode): + def __init__(self, case, expression, branches): + self.expression = expression + self.branches = branches + self.line = case.line + self.column = case.column + + +class AssignNode(ExpressionNode): + def __init__(self, idx, expression): + self.id = idx + self.expression = expression + self.line = idx.line + self.column = idx.column + + +class UnaryNode(ExpressionNode): + def __init__(self, expression): + self.expression = expression + self.line = expression.line + self.column = expression.column + + +class NotNode(UnaryNode): + def __init__(self, notx, expression): + super().__init__(expression) + self.line = notx.line + self.column = notx.column + + +class BinaryNode(ExpressionNode): + def __init__(self, left, operator, right): + self.left = left + self.right = right + self.line = operator.line + self.column = operator.column + + +class LessEqualNode(BinaryNode): + pass + + +class LessNode(BinaryNode): + pass + + +class EqualNode(BinaryNode): + pass + + +class ArithmeticNode(BinaryNode): + pass + + +class PlusNode(ArithmeticNode): + pass + + +class MinusNode(ArithmeticNode): + pass + + +class StarNode(ArithmeticNode): + pass + + +class DivNode(ArithmeticNode): + pass + + +class IsVoidNode(UnaryNode): + def __init__(self, isvoid, expression): + super().__init__(expression) + self.line = isvoid.line + self.column = isvoid.column + + +class ComplementNode(UnaryNode): + def __init__(self, complement, expression): + super().__init__(expression) + self.line = complement.line + self.column = complement.column + + +class FunctionCallNode(ExpressionNode): + def __init__(self, obj, idx, args, typex=None): + self.obj = obj + self.id = idx + self.args = args + self.type = typex + self.line = obj.line + self.column = obj.column + + +class MemberCallNode(ExpressionNode): + def __init__(self, idx, args): + self.id = idx + self.args = args + self.line = idx.line + self.column = idx.column + + +class NewNode(ExpressionNode): + def __init__(self, new, typex): + self.type = typex + self.line = new.line + self.column = new.column + + +class AtomicNode(ExpressionNode): + def __init__(self, token): + self.token = token + self.line = token.line + self.column = token.column + + +class IntegerNode(AtomicNode): + pass + + +class IdNode(AtomicNode): + pass + + +class StringNode(AtomicNode): + pass + + +class BoolNode(AtomicNode): + pass diff --git a/src/core/cool/CoolAstFormatter.py b/src/core/cool/CoolAstFormatter.py new file mode 100644 index 000000000..8d0d2f4bf --- /dev/null +++ b/src/core/cool/CoolAstFormatter.py @@ -0,0 +1,116 @@ +from core.tools import visitor +from core.cool.CoolAst import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode +from core.cool.CoolAst import IfThenElseNode, WhileLoopNode, BlockNode, LetInNode, CaseOfNode +from core.cool.CoolAst import AssignNode, UnaryNode, BinaryNode +from core.cool.CoolAst import FunctionCallNode, MemberCallNode, NewNode, AtomicNode + +# Visitor para imprimir el AST + +class FormatVisitor: + @visitor.on('node') + def visit(self, node, tabs): + pass + + @visitor.when(ProgramNode) + def visit(self, node, tabs=0): + ans = '\t' * tabs + f'\\__ProgramNode [ ... ]' + statements = '\n'.join(self.visit(child, tabs + 1) for child in node.declarations) + return f'{ans}\n{statements}' + + @visitor.when(ClassDeclarationNode) + def visit(self, node, tabs=0): + parent = '' if node.parent is None else f"inherits {node.parent.lex}" + ans = '\t' * tabs + f'\\__ClassDeclarationNode: class {node.id.lex} {parent} {{ ... }}' + features = '\n'.join(self.visit(child, tabs + 1) for child in node.features) + return f'{ans}\n{features}' + + @visitor.when(AttrDeclarationNode) + def visit(self, node, tabs=0): + ans = '\t' * tabs + f'\\__AttrDeclarationNode: {node.id.lex}: {node.type.lex}' + (' <- ' if node.expression else '') + ';' + expr = self.visit(node.expression, tabs + 1) if node.expression else None + return f'{ans}' + (f'\n{expr}' if expr else '') + + @visitor.when(FuncDeclarationNode) + def visit(self, node, tabs=0): + params = ', '.join(': '.join(tok.lex for tok in param) for param in node.params) + ans = '\t' * tabs + f'\\__FuncDeclarationNode: {node.id.lex}({params}): {node.type.lex} {{ }}' + body = self.visit(node.body, tabs + 1) + return f'{ans}\n{body}' + + @visitor.when(IfThenElseNode) + def visit(self, node, tabs=0): + ans = '\t' * tabs + f'\\_IfThenElseNode: if then else fi' + cond = self.visit(node.condition, tabs + 1) + if_body = self.visit(node.if_body, tabs + 1) + else_body = self.visit(node.else_body, tabs + 1) + return f'{ans}\n{cond}\n{if_body}\n{else_body}' + + @visitor.when(WhileLoopNode) + def visit(self, node, tabs=0): + ans = '\t' * tabs + f'\\_WhileNode: while loop pool' + cond = self.visit(node.condition, tabs + 1) + body = self.visit(node.body, tabs + 1) + return f'{ans}\n{cond}\n{body}' + + @visitor.when(BlockNode) + def visit(self, node, tabs=0): + ans = '\t' * tabs + f'\\_BlockNode: {{ ; ... ; }}' + expressions = '\n'.join(self.visit(expr, tabs + 1) for expr in node.expressions) + return f'{ans}\n{expressions}' + + @visitor.when(LetInNode) + def visit(self, node, tabs=0): + let_body = ', '.join(f'{idx.lex}: {typex.lex}' + (' <- ' if expr else '') for idx, typex, expr in node.let_body) + ans = '\t' * tabs + f'\\_LetInNode: let {let_body} in ' + lets = '\n'.join(self.visit(expr, tabs + 1) for _, _, expr in node.let_body if expr) + body = self.visit(node.in_body, tabs + 1) + return f'{ans}\n{lets}\n{body}' + + @visitor.when(CaseOfNode) + def visit(self, node, tabs=0): + case_body = ' '.join(f'{idx.lex}: {typex.lex} => ;' for idx, typex, expr in node.branches) + ans = '\t' * tabs + f'\\_CaseOfNode: case of {case_body} esac' + expression = self.visit(node.expression, tabs + 1) + body = '\n'.join(self.visit(expr, tabs + 1) for _, _, expr in node.branches) + return f'{ans}\n{expression}\n{body}' + + @visitor.when(AssignNode) + def visit(self, node, tabs=0): + ans = '\t' * tabs + f'\\_AssingNode: {node.id.lex} <- ' + expr = self.visit(node.expression, tabs + 1) + return f'{ans}\n{expr}' + + @visitor.when(UnaryNode) + def visit(self, node, tabs=0): + ans = '\t' * tabs + f'\\__{node.__class__.__name__} ' + expression = self.visit(node.expression, tabs + 1) + return f'{ans}\n{expression}' + + @visitor.when(BinaryNode) + def visit(self, node, tabs=0): + ans = '\t' * tabs + f'\\__ {node.__class__.__name__} ' + left = self.visit(node.left, tabs + 1) + right = self.visit(node.right, tabs + 1) + return f'{ans}\n{left}\n{right}' + + @visitor.when(FunctionCallNode) + def visit(self, node, tabs=0): + obj = self.visit(node.obj, tabs + 1) + typex = f'@{node.type.lex}' if node.type else '' + ans = '\t' * tabs + f'\\__FunctionCallNode: {typex}.{node.id.lex}(, ..., )' + args = '\n'.join(self.visit(arg, tabs + 1) for arg in node.args) + return f'{ans}\n{obj}\n{args}' + + @visitor.when(MemberCallNode) + def visit(self, node, tabs=0): + ans = '\t' * tabs + f'\\__MemberCallNode: {node.id.lex}(, ..., )' + args = '\n'.join(self.visit(arg, tabs + 1) for arg in node.args) + return f'{ans}\n{args}' + + @visitor.when(NewNode) + def visit(self, node, tabs=0): + return '\t' * tabs + f'\\__ NewNode: new {node.type.lex}' + + @visitor.when(AtomicNode) + def visit(self, node, tabs=0): + return '\t' * tabs + f'\\__ {node.__class__.__name__}: {node.token.lex}' \ No newline at end of file diff --git a/src/core/lexer/Lexer.py b/src/core/lexer/Lexer.py new file mode 100644 index 000000000..48ddbbf27 --- /dev/null +++ b/src/core/lexer/Lexer.py @@ -0,0 +1,272 @@ +import ply.lex as lex +from core.tools.Utils import Token +from core.parser.Parser import CoolGrammar +from core.tools.Errors import LexicographicError + +class Lexer: + states = ( + ('comment', 'exclusive'), + ('string', 'exclusive') + ) + + # Palabras reservadas del lenguaje COOL + reserved = { + 'class': 'CLASS', + 'inherits': 'INHERITS', + 'function': 'FUNCTION', + 'if': 'IF', + 'then': 'THEN', + 'else': 'ELSE', + 'fi': 'FI', + 'while': 'WHILE', + 'loop': 'LOOP', + 'pool': 'POOL', + 'let': 'LET', + 'in': 'IN', + 'case': 'CASE', + 'of': 'OF', + 'esac': 'ESAC', + 'new': 'NEW', + 'isvoid': 'ISVOID' + } + + t_ignore = ' \f\r\t\v' + t_comment_ignore = '' + t_string_ignore = '' + + tokens = [ + # Identifiers + 'TYPE', 'ID', + # Primitive data types + 'INTEGER', 'STRING', 'BOOL', + # Special keywords + 'ACTION', + # Operators + 'ASSIGN', 'LESS', 'LESSEQUAL', 'EQUAL', 'INT_COMPLEMENT', 'NOT', + # Literals + 'PLUS', 'MINUS', 'STAR', 'DIVIDE', 'COLON', 'SEMICOLON', + 'OPAR', 'CPAR', 'OCUR', 'CCUR', 'AT', 'DOT', 'COMMA', + ] + list(reserved.values()) + + tokens_dict = {} + for tok in tokens: + try: + tokens_dict[tok] = CoolGrammar[tok.lower()].Name + except: + pass + + tokens_dict['ACTION'] = CoolGrammar['=>'].Name + tokens_dict['ASSIGN'] = CoolGrammar['<-'].Name + tokens_dict['LESS'] = CoolGrammar['<'].Name + tokens_dict['LESSEQUAL'] = CoolGrammar['<='].Name + tokens_dict['EQUAL'] = CoolGrammar['='].Name + tokens_dict['INT_COMPLEMENT'] = CoolGrammar['~'].Name + + tokens_dict['PLUS'] = CoolGrammar['+'].Name + tokens_dict['MINUS'] = CoolGrammar['-'].Name + tokens_dict['STAR'] = CoolGrammar['*'].Name + tokens_dict['DIVIDE'] = CoolGrammar['/'].Name + tokens_dict['COLON'] = CoolGrammar[':'].Name + tokens_dict['SEMICOLON'] = CoolGrammar[';'].Name + tokens_dict['OPAR'] = CoolGrammar['('].Name + tokens_dict['CPAR'] = CoolGrammar[')'].Name + tokens_dict['OCUR'] = CoolGrammar['{'].Name + tokens_dict['CCUR'] = CoolGrammar['}'].Name + tokens_dict['AT'] = CoolGrammar['@'].Name + tokens_dict['DOT'] = CoolGrammar['.'].Name + tokens_dict['COMMA'] = CoolGrammar[','].Name + + + def __init__(self): + self.lexer = lex.lex(module=self) + self.comment_level = 0 + self.code = '' + self.current_string = '' + self.errors = [] + + + # Expresiones regulares + + def t_INTEGER(self, t): + r'[0-9]+' + t.value = int(t.value) + return t + + def t_BOOL(self, t): + r't[rR][uU][eE]|f[aA][lL][sS][eE]' + t.value = True if t.value == 'true' else False + return t + + + # Other tokens with precedence before TYPE and ID + + def t_NOT(self, t): + r'[nN][oO][tT]' + return t + + # Identifiers + + def t_TYPE(self, t): + r'[A-Z][A-Za-z0-9_]*' + + try: + t.type = self.reserved[t.value.lower()] + except KeyError: + pass + + return t + + def t_ID(self, t): + r'[a-z][A-Za-z0-9_]*' + + try: + t.type = self.reserved[t.value.lower()] + except KeyError: + pass + + return t + + + t_ASSIGN = r'<-' + t_LESS = r'<' + t_LESSEQUAL = r'<=' + t_EQUAL = r'=' + t_INT_COMPLEMENT = r'~' + t_ACTION = r'=>' + + t_PLUS = r'\+' + t_MINUS = r'-' + t_STAR = r'\*' + t_DIVIDE = r'/' + t_COLON = r':' + t_SEMICOLON = r';' + t_OPAR = r'\(' + t_CPAR = r'\)' + t_OCUR = r'{' + t_CCUR = r'}' + t_AT = r'@' + t_DOT = r'\.' + t_COMMA = r',' + + #################### + ##### COMMENTS ##### + #################### + def t_LINECOMMENT(self, t): + r'--.*' + pass + + def t_COMMENTBEGIN(self, t): + r'\(\*' + self.comment_level += 1 + t.lexer.begin('comment') + + def t_comment_COMMENTBEGIN(self, t): + r'\(\*' + self.comment_level += 1 + + def t_comment_COMMENTEND(self, t): + r'\*\)' + self.comment_level -= 1 + if self.comment_level == 0: + t.lexer.begin('INITIAL') + + def t_comment_eof(self, t): + self.errors.append(LexicographicError(t.lineno, + self.find_column(t), 'EOF in comment')) + self.lexer.begin('INITIAL') + + def t_comment_error(self, t): + t.lexer.skip(1) + + ############################ + ##### STRING CONSTANTS ##### + ############################ + + def t_STRINGBEGIN(self, t): + r'"' + self.current_string = '' + self.lexer.begin('string') + + def t_string_NULL(self, t): + r'\0' + self.errors.append(LexicographicError(t.lineno, + self.find_column(t), 'Null caracter in string')) + self.lexer.begin('INITIAL') + + def t_string_NEWLINE(self, t): + r'\\\n' + self.current_string += '\n' + t.lexer.lineno += 1 + + def t_string_INVALID_NEWLINE(self, t): + r'\n' + t.lexer.lineno += 1 + self.errors.append(LexicographicError(t.lineno, + self.find_column(t), 'Unterminated string constant')) + self.lexer.begin('INITIAL') + + def t_string_SCAPED_SPECIAL_CHARACTER(self, t): + r'\\(b|t|f)' + self.current_string += t.value + + def t_string_SCAPED_CHARACTER(self, t): + r'\\.' + self.current_string += '\\' + t.value[1] + + def t_string_eof(self, t): + self.errors.append(LexicographicError(t.lineno, + self.find_column(t), 'EOF in string constant')) + self.lexer.begin('INITIAL') + + def t_string_STRINGEND(self, t): + r'"' + t.value = self.current_string + t.type = 'STRING' + self.lexer.begin('INITIAL') + return t + + def t_string_CHARACTER(self, t): + r'.' + self.current_string += t.value + + def t_string_error(self, t): + return t + + ########################### + ###### SPECIAL RULES ###### + ########################### + + def t_ANY_newline(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + + def find_column(self, token): + line_start = self.code.rfind('\n', 0, token.lexpos) + 1 + return (token.lexpos - line_start) + 1 \ + + 3 * len([i for i in self.code[line_start:token.lexpos] if i == '\t']) + + def t_error(self, t): + self.errors.append(LexicographicError(t.lineno, + self.find_column(t), f'ERROR "{t.value[0]}"')) + t.lexer.skip(1) + + + ''' + Dado un string retorna el arreglo de tokens resultante de analizar dicho string + ''' + def tokenize(self, code): + tokens = [] + self.code = code + + self.lexer.input(code) + while True: + token = self.lexer.token() + if token is None: + break + + tokens.append(Token(token.value, self.tokens_dict[token.type], + token.lineno, self.find_column(token))) + + tokens.append(Token('$', CoolGrammar.EOF.Name)) + + return tokens diff --git a/src/core/mips/CilToMipsVisitor.py b/src/core/mips/CilToMipsVisitor.py new file mode 100644 index 000000000..4e8271284 --- /dev/null +++ b/src/core/mips/CilToMipsVisitor.py @@ -0,0 +1,1004 @@ +import core.cil.CilAst as cil +import core.mips.MipsAst as mips +from ..tools import visitor + + +class CILToMIPSVisitor: + def __init__(self): + self.type_label_count = 0 + self.data_label_count = 0 + self.code_label_count = 0 + + self._types_section = {} + self._data_section = {} + self._functions_section = {} + + self._current_function = None + self._function_names = {} + self._pushed_args = 0 + self._labels = {} + + self.registers = mips.REGISTERS + + def generate_type_label(self): + self.type_label_count += 1 + return f'type_{self.type_label_count}' + + def generate_data_label(self): + self.data_label_count += 1 + return f'data_{self.data_label_count}' + + def generate_code_label(self): + self.code_label_count += 1 + return f'label_{self.code_label_count}' + + def enter_function(self, name, function): + self._functions_section[name] = function + self._current_function = function + self._labels = {} + + def exit_function(self): + self._current_function = None + + def get_free_register(self): + r, rest = self.registers + self.registers = rest + return r + + def free_register(self, reg): + self.registers.append(reg) + + def register_label(self, old_label, new_label): + self._labels[old_label] = new_label + + def get_label(self, label): + return self._labels[label] + + def get_var_location(self, node): + if isinstance(node, cil.AttributeNode): + return mips.RegisterRelativeLocation(mips.SP_REG, 0) + if isinstance(node, str): + return self._current_function.get_var_location(node) + return self._current_function.get_var_location(node.name) + + @visitor.on('node') + def collect_func_names(self, node): + pass + + @visitor.when(cil.ProgramNode) + def collect_func_names(self, node): + for f in node.dotcode: + self.collect_func_names(f) + + @visitor.when(cil.FunctionNode) + def collect_func_names(self, node): + if node.name == "entry": + self._function_names[node.name] = 'main' + else: + self._function_names[node.name] = node.name + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(cil.ProgramNode) + def visit(self, node): + self.collect_func_names(node) + self._data_section["default_str"] = mips.StringConst("default_str", "", line=node.line, column=node.column) + + for i in node.dottypes: + self.visit(i) + for i in node.dotdata: + self.visit(i) + for i in node.dotcode: + self.visit(i) + + return mips.ProgramNode([i for i in self._data_section.values()], + [i for i in self._types_section.values()], + [i for i in self._functions_section.values()], + line=node.line, column=node.column) + + @visitor.when(cil.TypeNode) + def visit(self, node): + name_label = self.generate_data_label() + self._data_section[node.name] = mips.StringConst(name_label, node.name, line=node.line, column=node.column) + + type_label = self.generate_type_label() + methods = [self._function_names[key] for key in node.methods.values()] + defaults = [] + if node.name == "String": + defaults = [('value', 'default_str'), ('len', 'type_4_proto')] + + type = mips.MIPSType(type_label, name_label, list(node.attributes.keys()), methods, + len(self._types_section), defaults) + self._types_section[node.name] = type + + @visitor.when(cil.DataNode) + def visit(self, node): + label = self.generate_data_label() + self._data_section[node.name] = mips.StringConst(label, node.value, line=node.line, column=node.column) + + @visitor.when(cil.FunctionNode) + def visit(self, node): + label = self._function_names[node.name] + params = [param.name for param in node.params] + localvars = [local.name for local in node.localvars] + size_for_locals = len(localvars) * mips.DOUBLE_WORD + + new_func = mips.FunctionNode(label, params, localvars, line=node.line, column=node.column) + self.enter_function(node.name, new_func) + self._current_function = new_func + self._labels = {} + + for instruction in node.instructions: + if isinstance(instruction, cil.LabelNode): + mips_label = self.generate_code_label() + self.register_label(instruction.label, mips_label) + + instructions = [] + instructions.extend(mips.push_register(mips.RA_REG, node.line, node.column)) + instructions.extend(mips.push_register(mips.FP_REG, line=node.line, column=node.column)) + instructions.append(mips.AdditionInmediateNode(mips.FP_REG, mips.SP_REG, 8, line=node.line, column=node.column)) + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, -size_for_locals, + line=node.line, column=node.column)) + + for i in node.instructions: + instructions.extend(self.visit(i)) + + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, size_for_locals, + line=node.line, column=node.column)) + instructions.extend(mips.pop_register(mips.FP_REG, line=node.line, column=node.column)) + instructions.extend(mips.pop_register(mips.RA_REG, node.line, node.column)) + + if self._current_function.label != 'main': + instructions.append(mips.JumpRegisterNode(mips.RA_REG, line=node.line, column=node.column)) + else: + instructions.append(mips.LoadInmediateNode(mips.V0_REG, 10, line=node.line, column=node.column)) + instructions.append(mips.SyscallNode()) + + new_func.instructions = instructions + self._current_function = None + + @visitor.when(cil.AssignNode) + def visit(self, node): + instructions = [] + + if isinstance(node.source, cil.VoidNode): + reg1 = mips.ZERO_REG + elif isinstance(node.source, int): + reg1 = mips.REGISTERS[0] + instructions.append(mips.LoadInmediateNode(reg1, int(node.source), line=node.line, column=node.column)) + else: + reg1 = mips.REGISTERS[0] + instructions.extend(self.visit(node.source)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.source), + line=node.line, column=node.column)) + if isinstance(node.source, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + if isinstance(node.dest, cil.AttributeNode): + self_var = self._current_function.params[0] + reg2 = mips.REGISTERS[1] + instructions.append(mips.LoadWordNode(reg2, self.get_var_location(self_var), node.line, node.column)) + + index = self._types_section[node.dest.type].attributes.index(node.dest.name) + 3 + instructions.append(mips.AdditionInmediateNode(reg2, reg2, index * 4, + line=node.line, column=node.column)) + instructions.append(mips.StoreWordNode(reg1, mips.RegisterRelativeLocation(reg2, 0), + line=node.line, column=node.column)) + else: + instructions.append(mips.StoreWordNode(reg1, self.get_var_location(node.dest), + line=node.line, column=node.column)) + + return instructions + + @visitor.when(cil.PlusNode) + def visit(self, node): + instructions = [] + + reg1 = mips.REGISTERS[0] + if isinstance(node.left, int): + instructions.append(mips.LoadInmediateNode(reg1, node.left, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.left)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.left), + line=node.line, column=node.column)) + if isinstance(node.left, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg2 = mips.REGISTERS[1] + if isinstance(node.right, int): + instructions.append(mips.LoadInmediateNode(reg2, node.right, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.right)) + instructions.append(mips.LoadWordNode(reg2, self.get_var_location(node.right), + line=node.line, column=node.column)) + if isinstance(node.right, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg3 = mips.REGISTERS[0] + instructions.append(mips.AdditionNode(reg3, reg1, reg2, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg3, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.MinusNode) + def visit(self, node): + instructions = [] + + reg1 = mips.REGISTERS[0] + if isinstance(node.left, int): + instructions.append(mips.LoadInmediateNode(reg1, node.left, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.left)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.left), + line=node.line, column=node.column)) + if isinstance(node.left, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg2 = mips.REGISTERS[1] + if isinstance(node.right, int): + instructions.append(mips.LoadInmediateNode(reg2, node.right, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.right)) + instructions.append(mips.LoadWordNode(reg2, self.get_var_location(node.right), + line=node.line, column=node.column)) + if isinstance(node.right, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg3 = mips.REGISTERS[0] + instructions.append(mips.SubstractionNode(reg3, reg1, reg2, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg3, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.StarNode) + def visit(self, node): + instructions = [] + + reg1 = mips.REGISTERS[0] + if isinstance(node.left, int): + instructions.append(mips.LoadInmediateNode(reg1, node.left, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.left)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.left), + line=node.line, column=node.column)) + if isinstance(node.left, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg2 = mips.REGISTERS[1] + if isinstance(node.right, int): + instructions.append(mips.LoadInmediateNode(reg2, node.right, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.right)) + instructions.append(mips.LoadWordNode(reg2, self.get_var_location(node.right), + line=node.line, column=node.column)) + if isinstance(node.right, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg3 = mips.REGISTERS[0] + instructions.append(mips.MultiplyNode(reg3, reg1, reg2, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg3, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.DivNode) + def visit(self, node): + instructions = [] + + reg1 = mips.REGISTERS[0] + if isinstance(node.left, int): + instructions.append(mips.LoadInmediateNode(reg1, node.left, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.left)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.left), + line=node.line, column=node.column)) + if isinstance(node.left, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg2 = mips.REGISTERS[1] + if isinstance(node.right, int): + instructions.append(mips.LoadInmediateNode(reg2, node.right, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.right)) + instructions.append(mips.LoadWordNode(reg2, self.get_var_location(node.right), + line=node.line, column=node.column)) + if isinstance(node.right, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + instructions.append(mips.DivideNode(reg1, reg2, line=node.line, column=node.column)) + instructions.append(mips.MoveLowNode(reg1, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg1, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.AllocateNode) + def visit(self, node): + instructions = [] + + if isinstance(node.type, int): + type = node.type + else: + type = self._types_section[node.type].index + + reg1 = mips.REGISTERS[0] + reg2 = mips.REGISTERS[1] + + instructions.append(mips.LoadInmediateNode(reg1, type, line=node.line, column=node.column)) + instructions.extend(mips.create_object(reg1, reg2, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.TypeOfNode) + def visit(self, node): + instructions = [] + + reg1 = mips.REGISTERS[0] + reg2 = mips.REGISTERS[1] + + instructions.extend(self.visit(node.obj)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.obj), + line=node.line, column=node.column)) + if isinstance(node.obj, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.LoadWordNode(reg2, mips.RegisterRelativeLocation(reg1, 0), + line=node.line, column=node.column)) + instructions.append(mips.ShiftLeftNode(reg2, reg2, 2, node.line, node.column)) + instructions.append(mips.LoadAddressNode(reg1, mips.TYPES_LABEL, node.line, node.column)) + instructions.append(mips.AdditionNode(reg1, reg1, reg2, node.line, node.column)) + instructions.append(mips.LoadWordNode(reg2, mips.RegisterRelativeLocation(reg1, 0), + line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg2, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.StaticCallNode) + def visit(self, node): + instructions = [] + func_name = self._function_names[node.function] + instructions.append(mips.JalNode(func_name, line=node.line, column=node.column)) + + if self._pushed_args > 0: + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, + self._pushed_args * mips.DOUBLE_WORD, + line=node.line, column=node.column)) + self._pushed_args = 0 + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.DynamicCallNode) + def visit(self, node): + instructions = [] + + type = self._types_section[node.type] + method = type.methods.index(self._function_names[node.method]) + + reg1 = mips.REGISTERS[0] + instructions.append(mips.LoadWordNode(reg1, mips.RegisterRelativeLocation(mips.SP_REG, 0), + line=node.line, column=node.column)) + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, node.line, node.column)) + self._pushed_args -= 1 + + instructions.append(mips.LoadWordNode(reg1, mips.RegisterRelativeLocation(reg1, 8), + line=node.line, column=node.column)) + instructions.append(mips.LoadWordNode(reg1, mips.RegisterRelativeLocation(reg1, method * 4), + line=node.line, column=node.column)) + instructions.append(mips.JalrNode(mips.RA_REG, reg1, line=node.line, column=node.column)) + + if self._pushed_args > 0: + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, + self._pushed_args * mips.DOUBLE_WORD, + line=node.line, column=node.column)) + self._pushed_args = 0 + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.ArgNode) + def visit(self, node): + self._pushed_args += 1 + instructions = [] + reg = mips.REGISTERS[0] + if isinstance(node.name, int): + instructions.append(mips.LoadInmediateNode(reg, node.name, + line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.name)) + instructions.append(mips.LoadWordNode(reg, self.get_var_location(node.name), + line=node.line, column=node.column)) + if isinstance(node.name, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.extend(mips.push_register(reg, line=node.line, column=node.column)) + return instructions + + @visitor.when(cil.ReturnNode) + def visit(self, node): + instructions = [] + + if node.value is None or isinstance(node.value, cil.VoidNode): + instructions.append(mips.LoadInmediateNode(mips.V0_REG, 0, line=node.value.line, column=node.value.column)) + elif isinstance(node.value, int): + instructions.append(mips.LoadInmediateNode(mips.V0_REG, node.value, + line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.value)) + instructions.append(mips.LoadWordNode(mips.V0_REG, self.get_var_location(node.value), + line=node.line, column=node.column)) + if isinstance(node.value, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + return instructions + + @visitor.when(cil.LoadNode) + def visit(self, node): + instructions = [] + + reg = mips.REGISTERS[0] + instructions.append(mips.LoadAddressNode(reg, self._data_section[node.msg.name].label, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg, self.get_var_location(node.dest) + , line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.PrintStringNode) + def visit(self, node: cil.PrintStringNode): + instructions = [] + instructions.append(mips.LoadInmediateNode(mips.V0_REG, 4, line=node.line, column=node.column)) + instructions.extend(self.visit(node.str_addr)) + instructions.append(mips.LoadWordNode(mips.ARG_REGISTERS[0], self.get_var_location(node.str_addr), + line=node.line, column=node.column)) + if isinstance(node.str_addr, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.SyscallNode()) + return instructions + + @visitor.when(cil.PrintIntNode) + def visit(self, node: cil.PrintIntNode): + instructions = [] + instructions.append(mips.LoadInmediateNode(mips.V0_REG, 1, line=node.line, column=node.column)) + instructions.extend(self.visit(node.value)) + instructions.append(mips.LoadWordNode(mips.ARG_REGISTERS[0], self.get_var_location(node.value), + line=node.line, column=node.column)) + if isinstance(node.value, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.SyscallNode()) + return instructions + + @visitor.when(cil.ExitNode) + def visit(self, node: cil.ExitNode): + instructions = [] + instructions.append(mips.LoadInmediateNode(mips.V0_REG, 10, line=node.line, column=node.column)) + instructions.append(mips.SyscallNode()) + return instructions + + @visitor.when(cil.CopyNode) + def visit(self, node): + instructions = [] + + reg = mips.REGISTERS[0] + instructions.extend(self.visit(node.value)) + instructions.append(mips.LoadWordNode(reg, self.get_var_location(node.value), + line=node.line, column=node.column)) + if isinstance(node.value, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.LoadWordNode(mips.ARG_REGISTERS[0], mips.RegisterRelativeLocation(reg, 4), + line=node.line, column=node.column)) + instructions.append(mips.ShiftLeftNode(mips.ARG_REGISTERS[0], mips.ARG_REGISTERS[0], 2, + line=node.line, column=node.column)) + instructions.append(mips.JalNode("malloc", line=node.line, column=node.column)) + instructions.append(mips.MoveNode(mips.ARG_REGISTERS[2], mips.ARG_REGISTERS[0] + , line=node.line, column=node.column)) + instructions.append(mips.MoveNode(mips.ARG_REGISTERS[0], reg, line=node.line, column=node.column)) + instructions.append(mips.MoveNode(mips.ARG_REGISTERS[1], mips.V0_REG, line=node.line, column=node.column)) + instructions.append(mips.JalNode("copy", line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest) + , line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.GetAttribNode) + def visit(self, node: cil.GetAttribNode): + instructions = [] + + reg1 = mips.REGISTERS[0] + reg2 = mips.REGISTERS[1] + instructions.extend(self.visit(node.obj)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.obj), + line=node.line, column=node.column)) + if isinstance(node.obj, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + tp = self._types_section[node.computed_type] + offset = (tp.attributes.index(node.attr) + 3) * mips.DOUBLE_WORD + instructions.append(mips.LoadWordNode(reg2, mips.RegisterRelativeLocation(reg1, offset), + line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg2, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.ErrorNode) + def visit(self, node: cil.ErrorNode): + instructions = [] + + mips_label = self._data_section[node.data.name].label + instructions.append(mips.LoadInmediateNode(mips.V0_REG, 4, line=node.line, column=node.column)) + instructions.append(mips.LoadAddressNode(mips.ARG_REGISTERS[0], mips_label, line=node.line, column=node.column)) + instructions.append(mips.SyscallNode()) + instructions.append(mips.LoadInmediateNode(mips.V0_REG, 10, line=node.line, column=node.column)) + instructions.append(mips.SyscallNode()) + + return instructions + + @visitor.when(cil.ReadIntNode) + def visit(self, node: cil.ReadIntNode): + instructions = [] + + instructions.append(mips.LoadInmediateNode(mips.V0_REG, 5, line=node.line, column=node.column)) + instructions.append(mips.SyscallNode()) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.ReadStringNode) + def visit(self, node: cil.ReadStringNode): + instructions = [] + + instructions.append(mips.JalNode('read_string', node.line, node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest), node.line, node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + return instructions + + @visitor.when(cil.SetAttribNode) + def visit(self, node: cil.SetAttribNode): + instructions = [] + + tp = self._types_section[node.computed_type] + offset = (tp.attributes.index(node.attr) + 3) * mips.DOUBLE_WORD + + reg1 = mips.REGISTERS[0] + instructions.extend(self.visit(node.obj)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.obj), + line=node.line, column=node.column)) + if isinstance(node.obj, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg2 = mips.REGISTERS[1] + if type(node.value) == int: + instructions.append(mips.LoadInmediateNode(reg2, node.value, + line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.value)) + instructions.append(mips.LoadWordNode(reg2, self.get_var_location(node.value), + line=node.line, column=node.column)) + if isinstance(node.value, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + instructions.append(mips.StoreWordNode(reg2, mips.RegisterRelativeLocation(reg1, offset), + line=node.line, column=node.column)) + return instructions + + @visitor.when(cil.LessNode) + def visit(self, node: cil.LessNode): + instructions = [] + + reg1 = mips.REGISTERS[0] + instructions.extend(self.visit(node.left)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.left), + line=node.line, column=node.column)) + if isinstance(node.left, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg2 = mips.REGISTERS[1] + instructions.extend(self.visit(node.right)) + instructions.append(mips.LoadWordNode(reg2, self.get_var_location(node.right), + line=node.line, column=node.column)) + if isinstance(node.right, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.LessNode(reg2, reg1, reg2, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg2, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.GotoIfNode) + def visit(self, node: cil.GotoIfNode): + instructions = [] + mips_label = self.get_label(node.label) + + reg1 = mips.REGISTERS[0] + instructions.extend(self.visit(node.condition)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.condition), + line=node.line, column=node.column)) + if isinstance(node.condition, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.BranchOnNotEqualNode(reg1, mips.ZERO_REG, mips_label, + line=node.line, column=node.column)) + + return instructions + + @visitor.when(cil.GotoNode) + def visit(self, node: cil.GotoNode): + mips_label = self.get_label(node.label) + return [mips.JumpNode(mips_label, line=node.line, column=node.column)] + + @visitor.when(cil.LabelNode) + def visit(self, node: cil.LabelNode): + mips_label = self.get_label(node.label) + return [mips.LabelNode(mips_label, line=node.line, column=node.column)] + + @visitor.when(cil.SubstringNode) + def visit(self, node: cil.SubstringNode): + instructions = [] + + reg1 = mips.ARG_REGISTERS[0] + instructions.extend(self.visit(node.str_value)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.str_value), + line=node.line, column=node.column)) + if isinstance(node.str_value, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg1 = mips.ARG_REGISTERS[1] + if type(node.index) == int: + instructions.append(mips.LoadInmediateNode(reg1, node.index, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.index)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.index), + line=node.line, column=node.column)) + if isinstance(node.index, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg1 = mips.ARG_REGISTERS[2] + if type(node.index) == int: + instructions.append(mips.LoadInmediateNode(reg1, node.length, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.length)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.length), + line=node.line, column=node.column)) + if isinstance(node.length, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + instructions.append(mips.JalNode("substr", line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + return instructions + + @visitor.when(cil.ConcatNode) + def visit(self, node: cil.ConcatNode): + instructions = [] + + reg = mips.ARG_REGISTERS[0] + instructions.extend(self.visit(node.prefix)) + instructions.append( + mips.LoadWordNode(reg, self.get_var_location(node.prefix), line=node.line, column=node.column)) + if isinstance(node.prefix, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + reg = mips.ARG_REGISTERS[1] + instructions.extend(self.visit(node.suffix)) + instructions.append( + mips.LoadWordNode(reg, self.get_var_location(node.suffix), line=node.line, column=node.column)) + if isinstance(node.suffix, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + reg = mips.ARG_REGISTERS[2] + instructions.extend(self.visit(node.length)) + instructions.append( + mips.LoadWordNode(reg, self.get_var_location(node.length), line=node.line, column=node.column)) + if isinstance(node.length, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.JalNode("concat", line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + return instructions + + @visitor.when(cil.LengthNode) + def visit(self, node: cil.LengthNode): + instructions = [] + + reg = mips.ARG_REGISTERS[0] + instructions.extend(self.visit(node.source)) + instructions.append(mips.LoadWordNode(reg, self.get_var_location(node.source), + line=node.line, column=node.column)) + if isinstance(node.source, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + instructions.append(mips.JalNode("length", line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.EqualNode) + def visit(self, node: cil.EqualNode): + instructions = [] + + reg1 = mips.REGISTERS[0] + if type(node.left) == cil.VoidNode: + instructions.append(mips.LoadInmediateNode(reg1, 0, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.left)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.left), + line=node.line, column=node.column)) + if isinstance(node.left, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg2 = mips.REGISTERS[1] + if type(node.right) == cil.VoidNode: + instructions.append(mips.LoadInmediateNode(reg2, 0, line=node.line, column=node.column)) + else: + instructions.extend(self.visit(node.right)) + instructions.append(mips.LoadWordNode(reg2, self.get_var_location(node.right), + line=node.line, column=node.column)) + if isinstance(node.right, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + instructions.append(mips.EqualNode(reg1, reg1, reg2, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg1, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + return instructions + + @visitor.when(cil.NameNode) + def visit(self, node: cil.NameNode): + instructions = [] + + reg = mips.REGISTERS[0] + instructions.append(mips.LoadAddressNode(reg, mips.TYPES_LABEL, line=node.line, column=node.column)) + + tp_number = self._types_section[node.value].index + instructions.append(mips.AdditionInmediateNode(reg, reg, tp_number * 4, line=node.line, column=node.column)) + instructions.append(mips.LoadWordNode(reg, mips.RegisterRelativeLocation(reg, 0), + line=node.line, column=node.column)) + + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + return instructions + + @visitor.when(cil.EqualStringNode) + def visit(self, node: cil.EqualStringNode): + instructions = [] + + reg1 = mips.REGISTERS[0] + instructions.extend(self.visit(node.left)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.left), + line=node.line, column=node.column)) + if isinstance(node.left, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.MoveNode(mips.ARG_REGISTERS[0], reg1, line=node.line, column=node.column)) + + instructions.extend(self.visit(node.right)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.right), + line=node.line, column=node.column)) + if isinstance(node.right, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.MoveNode(mips.ARG_REGISTERS[1], reg1, line=node.line, column=node.column)) + + instructions.append(mips.JalNode("equal_str", line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(mips.V0_REG, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.ComplementNode) + def visit(self, node: cil.ComplementNode): + instructions = [] + + reg1 = mips.REGISTERS[0] + instructions.extend(self.visit(node.value)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.value), + line=node.line, column=node.column)) + if isinstance(node.value, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + instructions.append(mips.ComplementNode(reg1, reg1, line=node.line, column=node.column)) + instructions.append(mips.AdditionInmediateNode(reg1, reg1, 1, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg1, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.LessEqualNode) + def visit(self, node: cil.LessEqualNode): + instructions = [] + + reg1 = mips.REGISTERS[0] + instructions.extend(self.visit(node.left)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.left), + line=node.line, column=node.column)) + if isinstance(node.left, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + reg2 = mips.REGISTERS[1] + instructions.extend(self.visit(node.right)) + instructions.append(mips.LoadWordNode(reg2, self.get_var_location(node.right), + line=node.line, column=node.column)) + if isinstance(node.right, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + instructions.append(mips.LessEqualNode(reg1, reg1, reg2, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg1, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + return instructions + + ''' + @visitor.when(PrefixNode) + def visit(self, node: PrefixNode): + return f'PREFFIXNODE' + + @visitor.when(ToStrNode) + def visit(self, node: ToStrNode): + return f'{node.dest} = str({node.value})' + ''' + + @visitor.when(cil.NotNode) + def visit(self, node: cil.NotNode): + instructions = [] + + reg1 = mips.REGISTERS[0] + instructions.extend(self.visit(node.value)) + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(node.value), + line=node.line, column=node.column)) + if isinstance(node.value, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + instructions.append(mips.NotNode(reg1, reg1, line=node.line, column=node.column)) + instructions.extend(self.visit(node.dest)) + instructions.append(mips.StoreWordNode(reg1, self.get_var_location(node.dest), + line=node.line, column=node.column)) + if isinstance(node.dest, cil.AttributeNode): + instructions.append(mips.AdditionInmediateNode(mips.SP_REG, mips.SP_REG, 4, + node.line, node.column)) + + return instructions + + @visitor.when(cil.VarNode) + def visit(self, node: cil.VarNode): + return [] + + @visitor.when(cil.ParamNode) + def visit(self, node: cil.ParamNode): + return [] + + @visitor.when(cil.AttributeNode) + def visit(self, node: cil.AttributeNode): + instructions = [] + + self_var = self._current_function.params[0] + reg1 = mips.REGISTERS[4] + instructions.append(mips.LoadWordNode(reg1, self.get_var_location(self_var), node.line, node.column)) + + index = self._types_section[node.type].attributes.index(node.name) + 3 + instructions.append(mips.LoadWordNode(reg1, mips.RegisterRelativeLocation(reg1, index * 4), + line=node.line, column=node.column)) + instructions.extend(mips.push_register(reg1, node.line, node.column)) + return instructions \ No newline at end of file diff --git a/src/core/mips/MipsAst.py b/src/core/mips/MipsAst.py new file mode 100644 index 000000000..ddfe2331e --- /dev/null +++ b/src/core/mips/MipsAst.py @@ -0,0 +1,415 @@ +DOUBLE_WORD = 4 +REGISTERS_NAMES = ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7', 't8'] +ARG_REGISTERS_NAMES = ['a0', 'a1', 'a2', 'a3'] + +TYPES_LABEL = "types_table" +PROTOTYPE_LABEL = "prototype_table" + + +class Register: + def __init__(self, name): + self.name = name + + +REGISTERS = [Register(i) for i in REGISTERS_NAMES] +ARG_REGISTERS = [Register(i) for i in ARG_REGISTERS_NAMES] +FP_REG = Register('fp') +SP_REG = Register('sp') +RA_REG = Register('ra') +V0_REG = Register('v0') +V1_REG = Register('v1') +ZERO_REG = Register('zero') +LOW_REG = Register('lo') +GP_REG = Register('gp') + + +class Node: + def __init__(self, line=0, column=0): + self.line = line + self.column = column + + +class ProgramNode(Node): + def __init__(self, data, types, functions, line, column): + super().__init__(line, column) + self.data = data + self.types = types + self.functions = functions + + +class FunctionNode(Node): + def __init__(self, label, params, localvars, line, column): + super().__init__(line, column) + self.label = label + self.instructions = [] + self.params = params + self.localvars = localvars + + def add_instruction(self, instruction): + self.instructions.append(instruction) + + def get_param_stack_location(self, name): + index = self.params.index(name) + offset = ((len(self.params) - 1) - index) * DOUBLE_WORD + return RegisterRelativeLocation(FP_REG, offset) + + def get_local_stack_location(self, name): + index = self.localvars.index(name) + offset = (len(self.localvars) + 2 - index) * DOUBLE_WORD + return RegisterRelativeLocation(FP_REG, -offset) + + def get_var_location(self, name): + try: + return self.get_param_stack_location(name) + except ValueError: + return self.get_local_stack_location(name) + + +class InstructionNode(Node): + pass + + +class AbsoluteNode(InstructionNode): + # rdest <- abs(rsrc) + def __init__(self, rdest, rsrc, line, column): + ''' + Put the absolute value of register rsrc in register rdest + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc = rsrc + + +class AdditionNode(InstructionNode): + # rd <- rs + rt + def __init__(self, rdest, rsrc1, rsrc2, line, column): + ''' + Put the sum of registers rsrc1 and rsrc2 into register rdest. + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc1 = rsrc1 + self.rsrc2 = rsrc2 + + +class AdditionInmediateNode(InstructionNode): + def __init__(self, rdest, rsrc, imm, line, column): + ''' + Put the sum of register rsrc and the sign-extended immediate into register rdest + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc = rsrc + self.imm = imm + + +class DivideNode(InstructionNode): + def __init__(self, rsrc1, rsrc2, line, column): + ''' + Put the quotient of register rsrc1 and src2 into register Hi/Lo. + ''' + super().__init__(line, column) + self.rsrc1 = rsrc1 + self.rsrc2 = rsrc2 + +class MoveLowNode(InstructionNode): + + def __init__(self, dest, line, column): + ''' + Put the content of register low into register rsrc. + ''' + super().__init__(line, column) + self.dest = dest + +class MoveHighNode(InstructionNode): + + def __init__(self, dest, line, column): + ''' + Put the content of register Hi into register rsrc. + ''' + super().__init__(line, column) + self.dest = dest + + + +class MultiplyNode(InstructionNode): + def __init__(self, rdest, rsrc1, rsrc2, line, column): + ''' + Put the product of register rsrc1 and src2 into register rdest. + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc1 = rsrc1 + self.rsrc2 = rsrc2 + + +class NegateNode(InstructionNode): + def __init__(self, rdest, rsrc1, line, column): + ''' + Put the negation of register rsrc1 into register rdest. + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc1 = rsrc1 + + +class SubstractionNode(InstructionNode): + def __init__(self, rdest, rsrc1, rsrc2, line, column): + ''' + Put the difference of register rsrc1 and rsrc2 into register rdest. + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc1 = rsrc1 + self.rsrc2 = rsrc2 + + +class LessNode(InstructionNode): + def __init__(self, rdest, rsrc1, rsrc2, line, column): + ''' + Set register rdest to 1 if register rsrc1 is less than rsrc2, and 0 otherwise + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc1 = rsrc1 + self.rsrc2 = rsrc2 + + +class LessInmediateNode(InstructionNode): + def __init__(self, rdest, rsrc1, imm, line, column): + ''' + Set register rdest to 1 if register rsrc1 is less than imm, and 0 otherwise + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc1 = rsrc1 + self.imm = imm + + +class EqualNode(InstructionNode): + def __init__(self, rdest, rsrc1, rsrc2, line, column): + ''' + Set register rdest to 1 if register rsrc1 equals rsrc2, and 0 otherwise + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc1 = rsrc1 + self.rsrc2 = rsrc2 + + +class LessEqualNode(InstructionNode): + def __init__(self, rdest, rsrc1, rsrc2, line, column): + ''' + Set register rdest to 1 if register rsrc1 is less than or equal to rsrc2, and 0 otherwise + ''' + super().__init__(line, column) + self.rdest = rdest + self.rsrc1 = rsrc1 + self.rsrc2 = rsrc2 + + +class JumpNode(InstructionNode): + def __init__(self, label, line, column): + ''' + Unconditionally jump to the instruction at the label. + ''' + super().__init__(line, column) + self.label = label + +class JumpRegisterNode(InstructionNode): + def __init__(self, reg, line, column): + ''' + Unconditionally jump to the instruction at the label. + ''' + super().__init__(line, column) + self.reg = reg + + +class JalNode(InstructionNode): + def __init__(self, label, line, column): + ''' + Unconditionally jump to the instruction at target. Save the address of the next + instruction in register ra (rd said the manual). + ''' + super().__init__(line, column) + self.label = label + +class JalrNode(InstructionNode): + def __init__(self, reg1, reg2, line, column): + ''' + Unconditionally jump to the instruction at target register. Save the address of the next + instruction in register ra (rd said the manual). + ''' + super().__init__(line, column) + self.reg1 = reg1 + self.reg2 = reg2 + +class MoveNode(InstructionNode): + def __init__(self, reg1, reg2, line, column): + super().__init__(line, column) + self.reg1 = reg1 + self.reg2 = reg2 + + +class StoreWordNode(InstructionNode): + def __init__(self, reg, addr, line, column): + super().__init__(line, column) + self.reg = reg + self.addr = addr + + +class LoadInmediateNode(InstructionNode): + def __init__(self, reg, value, line, column): + super().__init__(line, column) + self.reg = reg + self.value = value + + +class LoadWordNode(InstructionNode): + def __init__(self, reg, addr, line, column): + super().__init__(line, column) + self.reg = reg + self.addr = addr + + +class LoadAddressNode(InstructionNode): + def __init__(self, reg, label, line, column): + ''' + Load computed address , not the contents of the location, into register rdest + ''' + super().__init__(line, column) + self.reg = reg + self.label = label + + +class BranchOnNotEqualNode(InstructionNode): + def __init__(self, reg1, reg2, label, line, column): + super().__init__(line, column) + self.reg1 = reg1 + self.reg2 = reg2 + self.label = label + + +class LabelNode(InstructionNode): + def __init__(self, name, line, column): + super().__init__(line, column) + self.name = name + + +class NotNode(InstructionNode): + def __init__(self, dest, src, line, column): + super().__init__(line, column) + self.dest = dest + self.src = src + +class ComplementNode(InstructionNode): + def __init__(self, dest, src, line, column): + super().__init__(line, column) + self.dest = dest + self.src = src + + +class ShiftLeftNode(InstructionNode): # Shift Left Logical + def __init__(self, dest, src, bits, line, column): + super().__init__(line, column) + self.dest = dest + self.src = src + self.bits = bits + + +class SyscallNode(InstructionNode): + pass + + +class DataNode(Node): + def __init__(self, label, line, column): + super().__init__(line, column) + self.label = label + + +class StringConst(DataNode): + def __init__(self, label, string, line, column): + super().__init__(label, line, column) + self.string = string + + +class MIPSType: + def __init__(self, label, name_addr, attributes, + methods, index, default=[]): + self.label = label + self.name = name_addr + self.attributes = attributes + self.default_attributes = dict(default) + self.methods = methods + self.index = index + + @property + def size(self): + return len(self.attributes) + DOUBLE_WORD + + @property + def string_name_label(self): + return self.name + + +class MemoryLocation: + pass + + +class RegisterRelativeLocation(MemoryLocation): + def __init__(self, register, offset): + self._register = register + self._offset = offset + + @property + def register(self): + return self._register + + @property + def offset(self): + return self._offset + + +class LabelRelativeLocation(MemoryLocation): + def __init__(self, label, offset): + self._label = label + self._offset = offset + + @property + def label(self): + return self._label + + @property + def offset(self): + return self._offset + + +def push_register(reg, line, column): + move_stack = AdditionInmediateNode(SP_REG, SP_REG, -DOUBLE_WORD, line, column) + save_location = RegisterRelativeLocation(SP_REG, 0) + save_register = StoreWordNode(reg, save_location, line, column) + return [move_stack, save_register] + + +def pop_register(reg, line, column): + load_value = LoadWordNode(reg, RegisterRelativeLocation(SP_REG, 0), line, column) + move_stack = AdditionInmediateNode(SP_REG, SP_REG, DOUBLE_WORD, line, column) + return [load_value, move_stack] + + +def create_object(reg1, reg2, line, column): + instructions = [] + + instructions.append(ShiftLeftNode(reg1, reg1, 2, line, column)) + instructions.append(LoadAddressNode(reg2, PROTOTYPE_LABEL, line, column)) + instructions.append(AdditionNode(reg2, reg2, reg1, line, column)) + instructions.append(LoadWordNode(reg2, RegisterRelativeLocation(reg2, 0), line, column)) + instructions.append(LoadWordNode(ARG_REGISTERS[0], RegisterRelativeLocation(reg2, 4), line, column)) + instructions.append(ShiftLeftNode(ARG_REGISTERS[0], ARG_REGISTERS[0], 2, line, column)) + instructions.append(JalNode("malloc", line, column)) + instructions.append(MoveNode(ARG_REGISTERS[2], ARG_REGISTERS[0], line, column)) + instructions.append(MoveNode(ARG_REGISTERS[0], reg2, line, column)) + instructions.append(MoveNode(ARG_REGISTERS[1], V0_REG, line, column)) + instructions.append(JalNode("copy", line, column)) + + return instructions diff --git a/src/core/mips/MipsAstFormatter.py b/src/core/mips/MipsAstFormatter.py new file mode 100644 index 000000000..c07c10925 --- /dev/null +++ b/src/core/mips/MipsAstFormatter.py @@ -0,0 +1,205 @@ +from core.tools import visitor +from core.mips.MipsAst import * + +class MIPSAstFormatter: + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node): + + data = f'.data\n' + '\n.word 0\n'.join(self.visit(i) for i in node.data) + '\n' + + names_table = f"{TYPES_LABEL}:\n" + "\n".join([f"\t.word\t{tp.name}" for tp in node.types]) + proto_table = f"{PROTOTYPE_LABEL}:\n" + "\n".join([f"\t.word\t{tp.label}_prototype" for tp in node.types]) + + types = "\n\n\n".join([self.visit(tp) for tp in node.types]) + code = '\n\n'.join(self.visit(i) for i in node.functions) + '\n' + + mipsCode = f'{data}\n\n{names_table}\n\n{proto_table}\n\n{types}\n\n\n.text\n.globl main\n{code}\n\n' + + with open('core/mips/MipsBasics.asm', 'r') as f: + mipsCode += "".join(f.readlines()) + return mipsCode + + + @visitor.when(MIPSType) + def visit(self, node): + methods = "\n".join([f"\t.word\t {m}" for m in node.methods]) + dispatch_table = f"{node.label}_dispatch:\n{methods}" + proto_begin = f"{node.label}_prototype:\n\t.word\t{node.index}\n\t.word\t{node.size}\n\t.word\t{node.label}_dispatch" + proto_attr = "\n".join([f'\t.word\t0' for attr in node.attributes]) + proto_end = f"\t.word\t-1" + proto = f"{proto_begin}\n{proto_attr}\n{proto_end}" if proto_attr != "" else f"{proto_begin}\n{proto_end}" + + return f'{dispatch_table}\n\n{proto}' + + @visitor.when(FunctionNode) + def visit(self, node): + return f'{node.label}:\n\t' + \ + f'\n\t'.join(self.visit(i) for i in node.instructions) + + @visitor.when(AbsoluteNode) + def visit(self, node): + return f'abs {self.visit(node.rdest)} {self.visit(node.rsrc)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(AdditionNode) + def visit(self, node): + return f'add {self.visit(node.rdest)} {self.visit(node.rsrc1)} {self.visit(node.rsrc2)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(AdditionInmediateNode) + def visit(self, node): + return f'addi {self.visit(node.rdest)} {self.visit(node.rsrc)} {self.visit(node.imm)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(DivideNode) + def visit(self, node): + return f'div {self.visit(node.rsrc1)} {self.visit(node.rsrc2)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(MultiplyNode) + def visit(self, node): + return f'mul {self.visit(node.rdest)} {self.visit(node.rsrc1)} {self.visit(node.rsrc2)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(NegateNode) + def visit(self, node): + return f'neg {self.visit(node.rdest)} {self.visit(node.rsrc1)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(SubstractionNode) + def visit(self, node): + return f'sub {self.visit(node.rdest)} {self.visit(node.rsrc1)} {self.visit(node.rsrc2)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(LessNode) + def visit(self, node): + return f'slt {self.visit(node.rdest)} {self.visit(node.rsrc1)} {self.visit(node.rsrc2)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(LessInmediateNode) + def visit(self, node): + return f'slti {self.visit(node.rdest)} {self.visit(node.rsrc1)} {self.visit(node.imm)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(EqualNode) + def visit(self, node): + return f'seq {self.visit(node.rdest)} {self.visit(node.rsrc1)} {self.visit(node.rsrc2)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(LessEqualNode) + def visit(self, node): + return f'sle {self.visit(node.rdest)} {self.visit(node.rsrc1)} {self.visit(node.rsrc2)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(JumpNode) + def visit(self, node): + return f'j {self.visit(node.label)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(JumpRegisterNode) + def visit(self, node): + return f'jr {self.visit(node.reg)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(JalNode) + def visit(self, node): + return f'jal {self.visit(node.label)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(JalrNode) + def visit(self, node): + return f'jalr {self.visit(node.reg1)} {self.visit(node.reg2)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(MoveNode) + def visit(self, node): + return f'move {self.visit(node.reg1)} {self.visit(node.reg2)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(StoreWordNode) + def visit(self, node): + return f'sw {self.visit(node.reg)} {self.visit(node.addr)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(LoadInmediateNode) + def visit(self, node): + return f'li {self.visit(node.reg)} {self.visit(node.value)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(LoadWordNode) + def visit(self, node): + return f'lw {self.visit(node.reg)} {self.visit(node.addr)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(LoadAddressNode) + def visit(self, node): + return f'la {self.visit(node.reg)} {self.visit(node.label)}' + + @visitor.when(BranchOnNotEqualNode) + def visit(self, node): + return f'bne {self.visit(node.reg1)} {self.visit(node.reg2)} {self.visit(node.label)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(LabelNode) + def visit(self, node): + return f'{self.visit(node.name)}:'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(NotNode) + def visit(self, node): + return f'xori {self.visit(node.dest)} {self.visit(node.src)} 1'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(ComplementNode) + def visit(self, node): + return f'not {self.visit(node.dest)} {self.visit(node.src)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(MoveLowNode) + def visit(self, node): + return f'mflo {self.visit(node.dest)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(MoveHighNode) + def visit(self, node): + return f'mfhi {self.visit(node.dest)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(ShiftLeftNode) + def visit(self, node): + return f'sll {self.visit(node.dest)} {self.visit(node.src)} {self.visit(node.bits)}'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(SyscallNode) + def visit(self, node): + return 'syscall'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(StringConst) + def visit(self, node): + return f'{node.label}: .asciiz "{node.string}"'.ljust(50) + \ + f'#line: {node.line} column: {node.column}' + + @visitor.when(RegisterRelativeLocation) + def visit(self, node): + return f'{node._offset}({self.visit(node._register)})' + + @visitor.when(LabelRelativeLocation) + def visit(self, node): + return f'{node._label}' + + @visitor.when(Register) + def visit(self, node): + return f'${node.name}' + + @visitor.when(int) + def visit(self, node): + return str(node) + + @visitor.when(str) + def visit(self, node): + return node \ No newline at end of file diff --git a/src/core/mips/MipsBasics.asm b/src/core/mips/MipsBasics.asm new file mode 100644 index 000000000..002c926a4 --- /dev/null +++ b/src/core/mips/MipsBasics.asm @@ -0,0 +1,378 @@ + +# Args: +# $a0 size to alloc +# Return: +# $v0 address of allocated block + +malloc: + addiu $sp $sp -12 # Save content of registers in sp + sw $a0 0($sp) + sw $t0 4($sp) + sw $t1 8($sp) + + li $t0 4 + div $a0 $t0 # Size of string / wordsize + mfhi $t1 # t2 holds remainder of division + + sub $t0 $t0 $t1 # Convert t1 to multiple of 4 + add $a0 $a0 $t0 + + li $v0 9 + syscall + + lw $a0 0($sp) # Return original content to registers + lw $t0 4($sp) + lw $t1 8($sp) + addiu $sp $sp 12 + jr $ra + + +# COPY +# $a0 address from +# $a1 address to +# $a2 size + +copy: + addiu $sp $sp -16 # Save content of registers in sp + sw $a0 0($sp) + sw $a1 4($sp) + sw $a2 8($sp) + sw $t0 12($sp) + +copy_loop: + beq $a2 $zero copy_end # Copy finished (copy size is 0) + lw $t0 0($a0) # Load in t0 content of source address a0 + sw $t0 0($a1) # Save in destiny a1 content of t0 + addiu $a0 $a0 4 # Increase source address a0 + addiu $a1 $a1 4 # Increase destiny addres a1 + addi $a2 $a2 -4 # Decrease copy size + j copy_loop + +copy_end: + lw $a0 0($sp) # Return original content to registers + lw $a1 4($sp) + lw $a2 8($sp) + lw $t0 12($sp) + addiu $sp $sp 16 + + jr $ra + + + + +# LEN +# a0 begin address +# return size in v0 +length: + # Save content of registers + addiu $sp $sp -8 + sw $t0 0($sp) + sw $t1 4($sp) + + move $t0 $a0 # Move to t0 the address to begin + move $v0 $zero # Set v0 to zero + +len_loop: + lb $t1 0($t0) # Save in t1 first byte of address + beq $t1 $zero len_end # Finish object if t1 is zero + addi $v0 $v0 1 # Increase count in v0 + addiu $t0 $t0 1 # Increase address pointer + j len_loop # Finish loop + +len_end: + # Return original content to registers + lw $t0 0($sp) + lw $t1 4($sp) + addiu $sp $sp 8 + + jr $ra + + +# SUBSTRING +# a0 Pointer to beginning of string +# a1 Pointer to beginning of substring +# a2 Size of substring + + +substr: + # Save content of registers + addiu $sp $sp -32 + sw $t0 0($sp) + sw $t1 4($sp) + sw $t2 8($sp) + sw $t3 12($sp) + sw $a0 16($sp) + sw $a1 20($sp) + sw $a2 24($sp) + sw $ra 28($sp) + + move $t0 $a0 # t0 points to beginning o string + add $t0 $t0 $a1 + li $t1 4 # t1 Word size + + div $a2 $t1 # Size of substring / wordsize + mfhi $t2 # t2 holds remainder of division + + bne $t2 $zero substr_allign_size # Branch if division is not exact + move $t1 $a2 # t1 size of substring + j substr_new_block + +substr_allign_size: + sub $t1 $t1 $t2 # Convert t1 to multiple of 4 to... + add $t1 $t1 $a2 # reserve memory via malloc + +substr_new_block: + move $a0 $t1 # Store in a0 size of space to reserve via malloc + jal malloc # Malloc + move $t3 $v0 # Pointer to beginning of reserved space + move $t1 $zero # Count + +substr_copy_loop: + beq $t1 $a2 substr_end # Copy finished + lb $t2 0($t0) # Load byte from string into t2 temporal + sb $t2 0($t3) # Savebyte from t2 into t3 + addiu $t0 $t0 1 # Increase pointer to string + addiu $t3 $t3 1 # Increase pointer to reserved space + addiu $t1 $t1 1 # Increase count + j substr_copy_loop + +substr_end: + sb $zero 0($t3) # Set next byte of substring to zero + + # Return original values to registers + lw $t0 0($sp) + lw $t1 4($sp) + lw $t2 8($sp) + lw $t3 12($sp) + lw $a0 16($sp) + lw $a1 20($sp) + lw $a2 24($sp) + lw $ra 28($sp) + addiu $sp $sp 32 + + jr $ra + + + +# CONCAT +# a0 pointer to string 1 +# a1 pointer to string 2 +# a2 size of string 1 + size of string 2 + +concat: + # Save content of registers + addiu $sp $sp -24 + sw $t0 0($sp) + sw $t1 4($sp) + sw $t2 8($sp) + sw $a0 12($sp) + sw $a1 16($sp) + sw $ra 20($sp) + + move $t0 $a0 # t0 pointer to string 1 + move $t1 $a1 # t1 pointer to string 2 + + + addiu $a0 $a2 1 # Save in a0 size in a2 + 1 + li $t2 4 # t2 = 4 + div $a0 $t2 # a0 / t2 + mfhi $a0 # a0 remainder of division + bne $a0 $zero concat_allign_size # Branch if size is multiple of 4 + addiu $a0 $a2 1 # Add 1 t size + j concat_size_alligned + +concat_allign_size: + sub $t2 $t2 $a0 # Convert t1 to multiple of 4 to... + add $a0 $a2 $t2 # reserve memory via malloc + addiu $a0 $a0 1 # Add 1 t size + +concat_size_alligned: + jal malloc # a0 stores size to reserve + move $t2 $v0 # t2 is pointer to empty space + j concat_copy_first_loop + +concat_copy_first_loop: + lb $a0 0($t0) # move to a0 content of t0 + beq $a0 $zero concat_copy_second_loop # a0 == 0 finish + sb $a0 0($t2) # move to t2 content of a0 + addiu $t0 $t0 1 # Increase t0 pointer + addiu $t2 $t2 1 # Increase t2 pointer + j concat_copy_first_loop + +concat_copy_second_loop: + lb $a0 0($t1) # move to a0 content of t1 + beq $a0 $zero concat_end # a0 == 0 finish + sb $a0 0($t2) # move to t2 content of a0 + addiu $t1 $t1 1 # Increase t1 pointer + addiu $t2 $t2 1 # Increase t2 pointer + j concat_copy_second_loop + +concat_end: + # Return original values to registers + sb $zero 0($t2) + lw $t0 0($sp) + lw $t1 4($sp) + lw $t2 8($sp) + lw $a0 12($sp) + lw $a1 16($sp) + lw $ra 20($sp) + addiu $sp $sp 24 + + jr $ra + +read_string: + addiu $sp $sp -28 + sw $ra 0($sp) + sw $t0 4($sp) + sw $t1 8($sp) + sw $a0 12($sp) + sw $a1 16($sp) + sw $a2 20($sp) + sw $t2 24($sp) + + li $t0 8 + + addi $a0 $t0 4 + jal malloc + move $t1 $v0 + move $t2 $zero + + read_string_loop: + + addu $a0 $t1 $t2 + subu $a1 $t0 $t2 + addu $t2 $t2 $a1 + jal read_string_up_to + + beq $v0 $zero read_string_not_finished + move $v0 $t1 + j read_string_finished + + read_string_not_finished: + sll $t0 $t0 1 + + addi $a0 $t0 4 + jal malloc + + move $a0 $t1 + move $t1 $v0 + move $a1 $v0 + move $a2 $t2 + jal copy + + j read_string_loop + + read_string_finished: + + lw $ra 0($sp) + lw $t0 4($sp) + lw $t1 8($sp) + lw $a0 12($sp) + lw $a1 16($sp) + lw $a2 20($sp) + lw $t2 24($sp) + addiu $sp $sp 28 + jr $ra + +# Lee a lo sumo $a1 caracteres y los guarda en la direccion +# a la que apunta $a0, $a1 debe ser divisible por 4 +# retorna en $v0 1 si se leyo un \n +read_string_up_to: + addiu $sp $sp -28 + sw $ra 0($sp) + sw $t0 4($sp) + sw $t1 8($sp) + sw $t2 12($sp) + sw $t3 16($sp) + sw $t4 20($sp) + sw $t5 24($sp) + + move $t0 $a0 + move $t1 $zero + li $t2 10 + addu $t3 $t0 $a1 + addiu $a1 $a1 1 + + li $v0 8 + syscall + lw $a0 0($a0) + beq $a0 $zero eol_terminated + + li $v0 0 + + eol_check: + beq $t0 $t3 read_loop_continue + + lb $t1 0($t0) + beq $t1 $t2 eol_terminated + + addiu $t0 $t0 1 + j eol_check + + eol_terminated: + sb $zero 0($t0) # put null character at the end of string + li $v0 1 + read_loop_continue: + + lw $ra 0($sp) + lw $t0 4($sp) + lw $t1 8($sp) + lw $t2 12($sp) + lw $t3 16($sp) + lw $t4 20($sp) + lw $t5 24($sp) + addiu $sp $sp 28 + jr $ra + + +# EQUAL STRING + +equal_str: + # a0 pointer to string 1 + # a1 pointer to string 2 + # v0 answer + + # Save content of registers + addiu $sp $sp -24 + sw $t0 0($sp) + sw $t1 4($sp) + sw $a0 8($sp) + sw $a1 12($sp) + sw $t2 16($sp) + sw $t3 20($sp) + + move $t0 $a0 + move $t1 $a1 + +equal_str_loop: + lb $t2 0($t0) + lb $t3 0($t1) + bne $t2 $t3 equal_str_different_strings + beq $t2 $zero equal_str_finished_first + beq $t3 $zero equal_str_finished_second + addi $t1 $t1 1 + addi $t0 $t0 1 + j equal_str_loop + +equal_str_different_strings: + move $v0 $zero + j equal_str_end + +equal_str_finished_first: + beq $t3 $zero equal_str_finished_second + move $v0 $zero + j equal_str_end + +equal_str_finished_second: + li $v0 1 + +equal_str_end: + lw $t0 0($sp) + lw $t1 4($sp) + lw $a0 8($sp) + lw $a1 12($sp) + lw $t2 16($sp) + lw $t3 20($sp) + addiu $sp $sp 24 + + jr $ra \ No newline at end of file diff --git a/src/core/parser/Parser.py b/src/core/parser/Parser.py new file mode 100644 index 000000000..4a393ecc2 --- /dev/null +++ b/src/core/parser/Parser.py @@ -0,0 +1,138 @@ +from core.tools.Pycompiler import Grammar +from core.tools.ParserLR1 import LR1Parser +from core.cool.CoolAst import * + + +# Representacion de la gramatica de COOL utilizando la clase grammar +CoolGrammar = Grammar() + +# noterminales +program = CoolGrammar.NonTerminal('', startSymbol=True) +class_list, def_class = CoolGrammar.NonTerminals(' ') +feature_list, feature = CoolGrammar.NonTerminals(' ') +param_list, param = CoolGrammar.NonTerminals(' ') +expr_1, expr_2, member_call, expr_list, let_list, case_list = CoolGrammar.NonTerminals( + ' ') +comp_expr, arith, arith_2, term, factor, factor_2 = CoolGrammar.NonTerminals( + ' ') +atom, func_call, arg_list = CoolGrammar.NonTerminals(' ') + +# terminales +classx, inherits, function = CoolGrammar.Terminals('class inherits function') +ifx, then, elsex, fi = CoolGrammar.Terminals('if then else fi') +whilex, loop, pool = CoolGrammar.Terminals('while loop pool') +let, inx = CoolGrammar.Terminals('let in') +case, of, esac = CoolGrammar.Terminals('case of esac') +semi, colon, comma, dot, at, opar, cpar, ocur, ccur, larrow, rarrow = CoolGrammar.Terminals( + '; : , . @ ( ) { } <- =>') +plus, minus, star, div, isvoid, compl = CoolGrammar.Terminals('+ - * / isvoid ~') +notx, less, leq, equal = CoolGrammar.Terminals('not < <= =') +new, idx, typex, integer, string, boolx = CoolGrammar.Terminals('new id type integer string bool') + +# Producciones +program %= class_list, lambda h, s: ProgramNode(s[1]) + +# Lista de clases +class_list %= def_class + class_list, lambda h, s: [s[1]] + s[2] +class_list %= def_class, lambda h, s: [s[1]] + +# Defincicion de la clase +def_class %= classx + typex + ocur + feature_list + ccur + semi, lambda h, s: ClassDeclarationNode(s[1], s[2], s[4]) +def_class %= classx + typex + inherits + typex + ocur + feature_list + ccur + semi, lambda h, s: ClassDeclarationNode( + s[1], s[2], s[6], s[4]) + +# Lista de propiedades de la clase +feature_list %= feature + feature_list, lambda h, s: [s[1]] + s[2] +feature_list %= CoolGrammar.Epsilon, lambda h, s: [] + +# Atributos de la clase +feature %= idx + colon + typex + semi, lambda h, s: AttrDeclarationNode(s[1], s[3]) +feature %= idx + colon + typex + larrow + expr_1 + semi, lambda h, s: AttrDeclarationNode(s[1], s[3], s[5]) + +# Metodos constructores de la clase +feature %= idx + opar + param_list + cpar + colon + typex + ocur + expr_1 + ccur + semi, lambda h, s: FuncDeclarationNode( + s[1], s[3], s[6], s[8]) +feature %= idx + opar + cpar + colon + typex + ocur + expr_1 + ccur + semi, lambda h, s: FuncDeclarationNode(s[1], [], + s[5], s[7]) +# Metodos de la clase +feature %= function + idx + opar + param_list + cpar + colon + typex + ocur + expr_1 + ccur + semi, lambda h, s: FuncDeclarationNode( + s[2], s[4], s[7], s[9]) +feature %= function + idx + opar + cpar + colon + typex + ocur + expr_1 + ccur + semi, lambda h, s: FuncDeclarationNode(s[2], [], + s[6], s[8]) +# Lista de parametros de funcion +param_list %= param, lambda h, s: [s[1]] +param_list %= param + comma + param_list, lambda h, s: [s[1]] + s[3] + +# parametro de funcion +param %= idx + colon + typex, lambda h, s: (s[1], s[3]) + +### Expresiones ### +# Expresion Let-in +expr_1 %= let + let_list + inx + expr_1, lambda h, s: LetInNode(s[1], s[2], s[4]) +let_list %= idx + colon + typex, lambda h, s: [(s[1], s[3], None)] +let_list %= idx + colon + typex + larrow + expr_1, lambda h, s: [(s[1], s[3], s[5])] +let_list %= idx + colon + typex + comma + let_list, lambda h, s: [(s[1], s[3], None)] + s[5] +let_list %= idx + colon + typex + larrow + expr_1 + comma + let_list, lambda h, s: [(s[1], s[3], s[5])] + s[7] + +expr_1 %= idx + larrow + expr_1, lambda h, s: AssignNode(s[1], s[3]) +expr_1 %= notx + expr_1, lambda h, s: NotNode(s[1], s[2]) +expr_1 %= expr_2 + equal + expr_1, lambda h, s: EqualNode(s[1], s[2], s[3]) +expr_1 %= expr_2, lambda h, s: s[1] + +expr_2 %= arith + less + arith, lambda h, s: LessNode(s[1], s[2], s[3]) +expr_2 %= arith + leq + arith, lambda h, s: LessEqualNode(s[1], s[2], s[3]) +expr_2 %= arith, lambda h, s: s[1] + +#Expresiones aritmeticas +arith %= arith + plus + factor, lambda h, s: PlusNode(s[1], s[2], s[3]) +arith %= arith + minus + factor, lambda h, s: MinusNode(s[1], s[2], s[3]) +arith %= factor, lambda h, s: s[1] + +factor %= factor + star + term, lambda h, s: StarNode(s[1], s[2], s[3]) +factor %= factor + div + term, lambda h, s: DivNode(s[1], s[2], s[3]) +factor %= term, lambda h, s: s[1] + +term %= compl + term, lambda h, s: ComplementNode(s[1], s[2]) +term %= isvoid + term, lambda h, s: IsVoidNode(s[1], s[2]) +term %= atom, lambda h, s: s[1] + +# Encapsulaciones atomicas +atom %= opar + expr_1 + cpar, lambda h, s: s[2] +atom %= integer, lambda h, s: IntegerNode(s[1]) +atom %= string, lambda h, s: StringNode(s[1]) +atom %= boolx, lambda h, s: BoolNode(s[1]) +atom %= idx, lambda h, s: IdNode(s[1]) +atom %= ifx + expr_1 + then + expr_1 + elsex + expr_1 + fi, lambda h, s: IfThenElseNode(s[1], s[2], s[4], s[6]) +atom %= whilex + expr_1 + loop + expr_1 + pool, lambda h, s: WhileLoopNode(s[1], s[2], s[4]) + +# Expresion new +atom %= new + typex, lambda h, s: NewNode(s[1], s[2]) + +# Encapsulamiento entre corchetes +atom %= ocur + expr_list + ccur, lambda h, s: BlockNode(s[1], s[2]) +expr_list %= expr_1 + semi, lambda h, s: [s[1]] +expr_list %= expr_1 + semi + expr_list, lambda h, s: [s[1]] + s[3] + +# Expresion Case of +atom %= case + expr_1 + of + case_list + esac, lambda h, s: CaseOfNode(s[1], s[2], s[4]) +case_list %= idx + colon + typex + rarrow + expr_1 + semi, lambda h, s: [(s[1], s[3], s[5])] +case_list %= idx + colon + typex + rarrow + expr_1 + semi + case_list, lambda h, s: [(s[1], s[3], s[5])] + s[7] + +atom %= func_call, lambda h, s: s[1] + +# Llamado a funcion +func_call %= atom + at + typex + dot + idx + opar + arg_list + cpar, lambda h, s: FunctionCallNode(s[1], s[5], s[7], s[3]) +func_call %= atom + at + typex + dot + idx + opar + cpar, lambda h, s: FunctionCallNode(s[1], s[5], [], s[3]) +func_call %= atom + dot + idx + opar + arg_list + cpar, lambda h, s: FunctionCallNode(s[1], s[3], s[5]) +func_call %= atom + dot + idx + opar + cpar, lambda h, s: FunctionCallNode(s[1], s[3], []) + +# Llamado a miembro de clase +func_call %= idx + opar + arg_list + cpar, lambda h, s: MemberCallNode(s[1], s[3]) +func_call %= idx + opar + cpar, lambda h, s: MemberCallNode(s[1], []) + +# Lista de argumentos +arg_list %= expr_1, lambda h, s: [s[1]] +arg_list %= expr_1 + comma + arg_list, lambda h, s: [s[1]] + s[3] + +# parser +CoolParser = LR1Parser(CoolGrammar) diff --git a/src/core/semantic/TypeBuilder.py b/src/core/semantic/TypeBuilder.py new file mode 100644 index 000000000..3c8624411 --- /dev/null +++ b/src/core/semantic/TypeBuilder.py @@ -0,0 +1,161 @@ +from core.tools import visitor +from core.tools.Semantic import * +from core.parser.Parser import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode +from core.tools.Errors import * + +# Visitor encargado de contruir los tipos. Una vez que se conocen los nombres +# de los tipos que intervienen en el codigo COOL, este visitor les annade sus +# metodos y atributos, asi como el tipo padre. + +class Type_Builder: + def __init__(self, Context : Context): + self.Context = Context + self.Current_Type = None + + self.errors = [] + + # Construye los tipos builtin + self.Object_Type = self.Context.get_type('Object') + + self.IO_Type = self.Context.get_type('IO') + self.IO_Type.set_parent(self.Object_Type) + self.IO_Type.depth = 1 + + self.Int_Type = self.Context.get_type('Int') + self.Int_Type.set_parent(self.Object_Type) + self.Int_Type.depth = 1 + self.Int_Type.sealed = True + + self.String_Type = self.Context.get_type('String') + self.String_Type.set_parent(self.Object_Type) + self.String_Type.depth = 1 + self.String_Type.sealed = True + + self.Bool_Type = self.Context.get_type('Bool') + self.Bool_Type.set_parent(self.Object_Type) + self.Bool_Type.depth = 1 + self.Bool_Type.sealed = True + + self.IO_Type.define_method('out_string', ['x'], [self.String_Type], SelfType(), 0, 0) + self.IO_Type.define_method('out_int', ['x'], [self.Int_Type], SelfType(), 0, 0) + self.IO_Type.define_method('in_int', [], [], self.Int_Type, 0, 0) + self.IO_Type.define_method('in_string', [], [], self.String_Type, 0, 0) + + self.Object_Type.define_method('abort', [], [], self.Object_Type, 0, 0) + self.Object_Type.define_method('type_name', [], [], self.String_Type, 0, 0) + self.Object_Type.define_method('copy', [], [], SelfType(), 0, 0) + + self.String_Type.define_method('length', [], [], self.Int_Type, 0, 0) + self.String_Type.define_method('concat', ['x'], [self.String_Type], self.String_Type, 0, 0) + self.String_Type.define_method('substr', ['l', 'r'], [self.Int_Type, self.Int_Type], self.String_Type, 0, 0) + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node : ProgramNode): + for type in node.declarations: + self.visit(type) + + try: + self.Context.get_type('Main').get_method('main') + except SemanticException: + # Cada programa COOL debe tener una clase MAIN + self.errors.append(SemanticError(0, 0, + f'Class Main and its method main must be defined')) + + @visitor.when(ClassDeclarationNode) + def visit(self, node : ClassDeclarationNode): + self.Current_Type = self.Context.get_type(node.id.lex) + + if node.parent: + try: + parent_type = self.Context.get_type(node.parent.lex) + except SemanticException as ex: + self.errors.append(TypeError(node.parent.line, node.parent.column, + f'Class {node.id} inherits from an undefined class {node.parent.lex}')) + parent_type = self.Object_Type + + try: + self.Current_Type.set_parent(parent_type) + except SemanticException as ex: + self.errors.append(SemanticError(node.parent.line, node.parent.column, + f'Class {node.id.lex} cannot inherit class {parent_type.name}')) + + parent = self.Current_Type.parent + # Revisa que no haya herencia ciclica + while parent: + if parent == self.Current_Type: + self.errors.append(SemanticError(node.line, node.column, + f'Class {node.id.lex}, or an ancestor of {node.id.lex}, ' + f'is involved in an inheritance cycle')) + self.Current_Type.parent = self.Object_Type + break + parent = parent.parent + + else: + self.Current_Type.set_parent(self.Object_Type) + + + for feat in node.features: + self.visit(feat) + + @visitor.when(AttrDeclarationNode) + def visit(self, node : AttrDeclarationNode): + try: + attr_type = self.Context.get_type(node.type.lex) + except SemanticException as ex: + # Existio un error al tratar de obtener el tipo del atributo + self.errors.append(TypeError(node.type.line, node.type.column, ex.text)) + attr_type = ErrorType() + + try: + self.Current_Type.define_attribute(node.id.lex, attr_type, node.line, node.column) + except SemanticException as ex: + # Existio un error al tratar de definir el atributo + self.errors.append(SemanticError(node.line, node.column, ex.text)) + + @visitor.when(FuncDeclarationNode) + def visit(self, node : FuncDeclarationNode): + param_names, param_types = [], [] + + for name, type in node.params: + try: + type = self.Context.get_type(type.lex) + except SemanticException as ex: + # Existio un error al tratar de obtener el tipo del parametro + self.errors.append(TypeError(type.line, type.column, + f'Class {type.lex} of formal parameter {name.lex} is undefined')) + type = ErrorType() + else: + if isinstance(type, SelfType): + self.errors.append(SemanticError(name.line, name.column, + f'\'self\' cannot be the name of a formal parameter')) + arg_type = ErrorType() + + if name.lex in param_names: + self.errors.append(SemanticError(name.line, name.column, + f'Formal parameter {name.lex} is multiply defined')) + + param_names.append(name.lex) + param_types.append(type) + + + + try: + return_type = self.Context.get_type(node.type.lex) + except SemanticException as ex: + # Existio un error al tratar de obtener el tipo del parametro de retorno + self.errors.append(TypeError(node.type.line, node.type.column, + f'Undefined return type {node.type.lex} in method {node.id.lex}')) + return_type = ErrorType() + + if return_type is SelfType: + return_type = self.Current_Type + + try: + self.Current_Type.define_method(node.id.lex, param_names, param_types, return_type, node.line, node.column) + except SemanticException as ex: + # Existio un error al tratar de definir el metodo + self.errors.append(SemanticError(node.line, node.column, ex.text)) diff --git a/src/core/semantic/TypeChecker.py b/src/core/semantic/TypeChecker.py new file mode 100644 index 000000000..11c3911a9 --- /dev/null +++ b/src/core/semantic/TypeChecker.py @@ -0,0 +1,519 @@ +from core.tools import visitor +from core.parser.Parser import ProgramNode, ClassDeclarationNode, AttrDeclarationNode, FuncDeclarationNode,\ + IfThenElseNode, WhileLoopNode, BlockNode, LetInNode, CaseOfNode,\ + AssignNode, LessEqualNode, LessNode, EqualNode, ArithmeticNode,\ + NotNode, IsVoidNode, ComplementNode, FunctionCallNode, MemberCallNode, NewNode,\ + IntegerNode, IdNode, StringNode, BoolNode +from core.tools.Semantic import Context, Scope, SelfType, SemanticException, ErrorType +from core.tools.Errors import * + +# Este es el visitor encargado de terminar el chequeo semantico. +# Revisa la compatibilidad de tipos, la compatibilidad en la herencia, +# que las variables hayan sido previamente definidas, asi como los +# metodos y atributos de clase, crea el scope para las variables, el +# cual sera rehusado para inferir las variables que se requieran. +# Observar que cada vez que el visitor llama recursivamente crea un scope +# hijo en el scope actual, esto se hace para que las variables previamente +# declaradas en ambitos hermanos no sean utiles en el ambito actual. + +class Type_Checker: + + def __init__(self, Context : Context): + self.Context = Context + self.errors = [] + self.Current_Type = None + self.Current_Method = None + + self.Object_Type = self.Context.get_type('Object') + self.IO_Type = self.Context.get_type('IO') + self.String_Type = self.Context.get_type('String') + self.Int_Type = self.Context.get_type('Int') + self.Bool_Type = self.Context.get_type('Bool') + + self.builtin_Types = [ + self.Object_Type, + self.IO_Type, + self.String_Type, + self.Int_Type, + self.Bool_Type + ] + + @visitor.on('node') + def visit(self, node, scope): + pass + + @visitor.when(ProgramNode) + def visit(self, node : ProgramNode, scope : Scope = None): + scope = Scope() + + # Calculate depth for classes + for declaration in node.declarations: + current = self.Context.get_type(declaration.id.lex) + parent = current.parent + while parent is not None: + current.depth += 1 + parent = parent.parent + + for declaration in node.declarations: + self.visit(declaration, scope.create_child()) + + return scope + + @visitor.when(ClassDeclarationNode) + def visit(self, node : ClassDeclarationNode, scope : Scope): + self.Current_Type = self.Context.get_type(node.id.lex) + + # Incluyo cada uno de los atributos de los padres en su respectivo orden + current = self.Current_Type + attributtes = [] + while current.parent: + current = current.parent + for att in reversed(current.attributes): + attributtes.append(att) + + scope.define_variable('self', self.Current_Type, node.line, node.column) + for att in reversed(attributtes): + scope.define_variable(att.name, att.type, att.line, att.column) + + for att in self.Current_Type.attributes: + if scope.is_defined(att.name): + self.errors.append(SemanticError(att.line, att.column, + f'Attribute {att.name} is an attribute of an inherited class')) + scope.define_variable(att.name, att.type, att.line, att.column) + + for feature in node.features: + self.visit(feature, scope.create_child()) + node.static_type = self.Current_Type + + @visitor.when(AttrDeclarationNode) + def visit(self, node : AttrDeclarationNode, scope : Scope): + expr = node.expression + attr = self.Current_Type.get_attribute(node.id.lex) + node_type = self.Current_Type if attr.type is SelfType else attr.type + + if expr: + self.visit(expr, scope.create_child()) + expr_type = expr.static_type + + # Chequeo compatibilidad de tipos + if not expr_type.conforms_to(node_type): + self.errors.append(TypeError(node.expression.line, node.expression.column, + f'Inferred type {expr_type.name} of initialization of attribute {attr.name} ' + f'does not conform to declared type {node_type.name}')) + + if attr.name.lower() == 'self': + self.errors.append(SemanticError(node.line, node.column, + '\'self\' cannot be the name of an attribute')) + + node.static_type = node_type + + @visitor.when(FuncDeclarationNode) + def visit(self, node : FuncDeclarationNode, scope : Scope): + self.Current_Method = self.Current_Type.get_method(node.id.lex) + + if self.Current_Type.parent: + try: + inherited_method = self.Current_Type.parent.get_method(node.id.lex) + + if len(self.Current_Method.param_names) != len(inherited_method.param_names): + self.errors.append(SemanticError(node.line, node.column, + f'Incompatible number of formal parameters in redefined method {self.Current_Method.name}')) + else: + for par1, par2, p in zip(self.Current_Method.param_types, inherited_method.param_types, node.params): + if par1.name != par2.name: + self.errors.append(SemanticError(p[0].line, p[0].column, + f'In redefined method {self.Current_Method.name}, parameter type {par1.name} ' + f'is different from original type {par2.name}')) + + if self.Current_Method.return_type.name != inherited_method.return_type.name: + self.errors.append(SemanticError(node.line, node.column, + f'In redefined method {self.Current_Method.name}, return type {self.Current_Method.return_type.name} ' + f'is different from original return type {inherited_method.return_type.name}')) + + except SemanticException: + pass + + + # Defino cada uno de los parametros de metodo + for pname, ptype in zip(self.Current_Method.param_names, self.Current_Method.param_types): + scope.define_variable(pname, ptype, node.line, node.column) + + if pname.lower() == 'self': + self.errors.append(SemanticError(node.line, node.column, + '\'self\' cannot be the name of a formal parameter')) + + # Chequeo consistencia en el cuerpo del metodo + self.visit(node.body, scope.create_child()) + + expr_type = node.body.static_type + return_type = self.Current_Method.return_type + + # Chequeo consistencia entre el tipo de retorno definido y el tipo de retorno + # del cuerpo del metodo + if not expr_type.conforms_to(return_type): + self.errors.append(TypeError(node.line, node.column, + f'Inferred return type {expr_type.name} of method {self.Current_Method.name} ' + f'does not conform to declared return type {return_type.name}')) + node.static_type = return_type + + @visitor.when(IfThenElseNode) + def visit(self, node : IfThenElseNode, scope : Scope): + # Chequeo consistencia en la condicion del if + self.visit(node.condition, scope.create_child()) + + condition_type = node.condition.static_type + # Chequeo que el tipo de la condicion sea booleano + if not condition_type.conforms_to(self.Bool_Type): + self.errors.append(TypeError(node.condition.line, node.condition.column, + 'Predicate of \'if\' does not have type Bool')) + + # Chequeo consistencia en las expresiones del then y el else + self.visit(node.if_body, scope.create_child()) + self.visit(node.else_body, scope.create_child()) + + if_type = node.if_body.static_type + else_type = node.else_body.static_type + + node.static_type = if_type.type_union(else_type) + + @visitor.when(WhileLoopNode) + def visit(self, node : WhileLoopNode, scope : Scope): + self.visit(node.condition, scope.create_child()) + condition_type = node.condition.static_type + + # Chequeo que la condicion sea de tipo booleano + if not condition_type.conforms_to(self.Bool_Type): + self.errors.append(TypeError(node.condition.line, node.condition.column, + 'Loop condition does not have type Bool')) + + # Chequeo consistencias en el cuerpo del while + self.visit(node.body, scope.create_child()) + + node.static_type = self.Object_Type + + @visitor.when(BlockNode) + def visit(self, node : BlockNode, scope : Scope): + + # Chequeo consistencias en cada una de las instrucciones del cuerpo del bloque + for expr in node.expressions: + self.visit(expr, scope.create_child()) + + node.static_type = node.expressions[-1].static_type + + @visitor.when(LetInNode) + def visit(self, node : LetInNode, scope : Scope): + for id, type, expr in node.let_body: + + if id.lex.lower() == 'self': + self.errors.append(SemanticError(id.line, id.column, + '\'self\' cannot be bound in a \'let\' expression')) + + # Por cada una de las declaraciones del let + try: + type = self.Context.get_type(type.lex) + except SemanticException as ex: + # Chequeo que el tipo exista + self.errors.append(TypeError(id.line, id.column, + f'Class {type.lex} of let-bound identifier {id.lex} is undefined')) + type = ErrorType() + + # Si es Self_Type tomo el tipo correspondiente + type = self.Current_Type if isinstance(type, SelfType) else type + + child = scope.create_child() + if expr: + # Chequeo consistencias en la declaracion y la compatibilidad de tipos + self.visit(expr, child) + if not expr.static_type.conforms_to(type): + self.errors.append(TypeError(id.line, id.column, + f'Inferred type {expr.static_type.name} of initialization of ' + f'{id.lex} does not conform to identifier\'s declared type {type.name}')) + + # Defino la variable + scope.define_variable(id.lex, type, node.line, node.column) + + # Chequeo consistencias en el cuerpo del let in + self.visit(node.in_body, scope.create_child()) + node.static_type = node.in_body.static_type + + @visitor.when(CaseOfNode) + def visit(self, node : CaseOfNode, scope : Scope): + # Chequeo consistencias en el case + self.visit(node.expression, scope.create_child()) + + branchs = [] + + node.static_type = None + for id, type, expr in node.branches: + # Por cada instruccion en el cuerpo del case-of + try: + type = self.Context.get_type(type.lex) + except SemanticException as ex: + # Chequeo que el tipo exista + self.errors.append(TypeError(type.line, type.column, + f'Class {type.lex} of case branch is undefined')) + type = ErrorType() + + # Chequeo que no sea un tipo especial + if isinstance(type, SelfType): + self.errors.append(SemanticError(id.line, id.column, + f'SELF_TYPE cannot be used as a case branch')) + + child = scope.create_child() + # Declaro la variable y chequeo consistencias en la expresion + child.define_variable(id.lex, type, node.line, node.column) + self.visit(expr, child) + + if type.name in branchs: + self.errors.append(SemanticError(id.line, id.column, + f'Duplicate branch {type.name} in case statement')) + branchs.append(type.name) + + node.static_type = node.static_type.type_union(expr.static_type) if node.static_type else expr.static_type + + + @visitor.when(AssignNode) + def visit(self, node : AssignNode, scope : Scope): + # Chequeo consistencias en la expresion + self.visit(node.expression, scope.create_child()) + expr_type = node.expression.static_type + + if node.id.lex.lower() == 'self': + self.errors.append(SemanticError(node.line, node.column, + 'Cannot assign to \'self\'')) + + # Chequeo que la variable este declarada y que su tipo sea valido + if scope.is_defined(node.id.lex): + var_type = scope.find_variable(node.id.lex).type + if isinstance(var_type, SelfType): + var_type = self.Current_Type + + if not expr_type.conforms_to(var_type): + self.errors.append(TypeError(node.expression.line, node.expression.column, + f'Inferred type {expr_type.name} of initialization of attribute {node.id.lex} ' + f'does not conform to declared type {var_type.name}')) + else: + self.errors.append(NameError(node.line, node.column, + f'Undeclared identifier {node.id.lex}')) + + node.static_type = expr_type + + @visitor.when(NotNode) + def visit(self, node : NotNode, scope : Scope): + # Chequeo la consistencia de la expresion + self.visit(node.expression, scope.create_child()) + + # Chequeo que la expresion sea booleana + if not node.expression.static_type.conforms_to(self.Bool_Type): + self.errors.append(TypeError(node.expression.line, node.expression.column, + f'Argument of \'not\' has type {node.expression.static_type.name} instead of Bool')) + + node.static_type = self.Bool_Type + + @visitor.when(LessEqualNode) + def visit(self, node : LessEqualNode, scope : Scope): + # Chequeo la consistencia de ambos miembros + self.visit(node.left, scope.create_child()) + self.visit(node.right, scope.create_child()) + left_type = node.left.static_type + right_type = node.right.static_type + + # Chequeo que ambos miembros posean tipo int + if not left_type.conforms_to(self.Int_Type) or not right_type.conforms_to(self.Int_Type): + self.errors.append(TypeError(node.line, node.column, + f'non-Int arguments: {left_type.name} <= {right_type.name}')) + + node.static_type = self.Bool_Type + + @visitor.when(LessNode) + def visit(self, node: LessNode, scope: Scope): + # Chequeo la consistencia de ambos miembros + self.visit(node.left, scope.create_child()) + self.visit(node.right, scope.create_child()) + left_type = node.left.static_type + right_type = node.right.static_type + + # Chequeo que ambos miembros posean tipo int + if not left_type.conforms_to(self.Int_Type) or not right_type.conforms_to(self.Int_Type): + self.errors.append(TypeError(node.line, node.column, + f'non-Int arguments: {left_type.name} < {right_type.name}')) + + node.static_type = self.Bool_Type + + @visitor.when(EqualNode) + def visit(self, node: EqualNode, scope: Scope): + # Chequeo la consistencia de ambos miembros + self.visit(node.left, scope.create_child()) + self.visit(node.right, scope.create_child()) + left_type = node.left.static_type + right_type = node.right.static_type + + # Chequeo que ambos miembros posean tipos comparables + if left_type.conforms_to(self.Int_Type) ^ right_type.conforms_to(self.Int_Type): + self.errors.append(TypeError(node.line, node.column, + f'Illegal comparison with a basic type')) + elif left_type.conforms_to(self.String_Type) ^ right_type.conforms_to(self.String_Type): + self.errors.append(TypeError(node.line, node.column, + f'Illegal comparison with a basic type')) + elif left_type.conforms_to(self.Bool_Type) ^ right_type.conforms_to(self.Bool_Type): + self.errors.append(TypeError(node.line, node.column, + f'Illegal comparison with a basic type')) + + node.static_type = self.Bool_Type + + @visitor.when(ArithmeticNode) + def visit(self, node : ArithmeticNode, scope : Scope): + # Chequeo la consistencia de ambos miembros + self.visit(node.left, scope.create_child()) + self.visit(node.right, scope.create_child()) + left_type = node.left.static_type + right_type = node.right.static_type + + # Chequeo que ambos miembros posean tipo int + if not left_type.conforms_to(self.Int_Type) or not right_type.conforms_to(self.Int_Type): + self.errors.append(TypeError(node.line, node.column, + f'non-Int arguments: {left_type.name} and {right_type.name}')) + + node.static_type = self.Int_Type + + @visitor.when(IsVoidNode) + def visit(self, node : IsVoidNode, scope : Scope): + # Chequeo la consistencia de la expresion + self.visit(node.expression, scope.create_child()) + node.static_type = self.Bool_Type + + @visitor.when(ComplementNode) + def visit(self, node : ComplementNode, scope : Scope): + # Chequeo la consistencia de la expresion + self.visit(node.expression, scope.create_child()) + + # Chequeo que la expresion sea de tipo booleana + if not node.expression.static_type.conforms_to(self.Int_Type): + self.errors.append(TypeError(node.expression.line, node.expression.column, + f'Argument of \'~\' has type {node.expression.static_type.name} instead of Int')) + + node.static_type = self.Int_Type + + @visitor.when(FunctionCallNode) + def visit(self, node : FunctionCallNode, scope : Scope): + # Chequeo la consistencia de la expresion a la cual se le pide la funcion + self.visit(node.obj, scope.create_child()) + obj_type = node.obj.static_type + + try: + if node.type: + # Chequeo que el tipo exista + try: + node_type = self.Context.get_type(node.type.lex) + except SemanticException as ex: + self.errors.append(TypeError(node.line, node.column, + f'Class {node.type.lex} not defined')) + node_type = ErrorType() + + # Chequeo que el tipo no sea un tipo especial + if isinstance(node_type, SelfType): + self.errors.append(TypeError(node.line, node.column, + 'SELF_TYPE cannot be used in a dispatch')) + + # Chequeo que los tipos sean compatibles + if not obj_type.conforms_to(node_type): + self.errors.append(TypeError(node.line, node.column, + f'Expression type {obj_type.name} does not conform ' + f'to declared static dispatch type {node_type.name}')) + + obj_type = node_type + + + obj_method = obj_type.get_method(node.id.lex) + return_type = obj_type if isinstance(obj_method.return_type, SelfType) else obj_method.return_type + + except SemanticException as ex: + self.errors.append(AttributeError(node.id.line, node.id.column, + f'Dispatch to undefined method {node.id.lex}')) + return_type = ErrorType() + obj_method = None + + # Chequeo consistencias en los argumentos con los que se llama al metodo + for arg in node.args: + self.visit(arg, scope.create_child()) + + if obj_method and len(node.args) == len(obj_method.param_types): + for arg, param_type, param_name in zip(node.args, obj_method.param_types, obj_method.param_names): + if not arg.static_type.conforms_to(param_type): + # Chequeo compatibilidad de tipos entre los argumentos + self.errors.append(TypeError(arg.line, arg.column, + f'In call of method {obj_method.name}, type {arg.static_type.name} of ' + f'parameter {param_name} does not conform to declared type {param_type.name}')) + + elif obj_method: + # Chequeo que la cantidad de argumentos sea igual a las solicitadas por el metodo + self.errors.append(SemanticError(node.id.line, node.id.column, + f'Method {obj_method.name} called with wrong number of arguments')) + + node.static_type = return_type + + + @visitor.when(MemberCallNode) + def visit(self, node : MemberCallNode, scope : Scope): + # Chequeo que el metodo exista en el tipo actual + try: + obj_method = self.Current_Type.get_method(node.id.lex) + return_type = self.Current_Type if isinstance(obj_method.return_type, SelfType) else obj_method.return_type + except SemanticException as ex: + self.errors.append(AttributeError(node.id.line, node.id.column, + f'Dispatch to undefined method {node.id.lex}')) + obj_method = None + return_type = ErrorType() + + # Chequeo la consistencia en los argumentos + for arg in node.args: + self.visit(arg, scope.create_child()) + + if obj_method and len(node.args) == len(obj_method.param_types): + # Chequeo la compatibiidad entre los tipos de los argumentos + for arg, param_type, param_name in zip(node.args, obj_method.param_types, obj_method.param_names): + if not arg.static_type.conforms_to(param_type): + self.errors.append(TypeError(arg.line, arg.column, + f'In call of method {obj_method.name}, type {arg.static_type.name} of ' + f'parameter {param_name} does not conform to declared type {param_type.name}')) + + elif obj_method: + # Chequeo que la cantidad de argumentos coincida con los que requiere el metodo + self.errors.append(SemanticError(node.id.line, node.id.column, + f'Method {obj_method.name} called with wrong number of arguments')) + + node.static_type = return_type + + @visitor.when(NewNode) + def visit(self, node : NewNode, scope : Scope): + # Chequeo que el tipo exista + try: + type = self.Context.get_type(node.type.lex) + except SemanticException as ex: + self.errors.append(TypeError(node.type.line, node.type.column, + f'\'new\' used with undeclared class {node.type.lex}')) + type = ErrorType() + + node.static_type = type + + @visitor.when(IntegerNode) + def visit(self, node : IntegerNode, scope : Scope): + node.static_type = self.Int_Type + + @visitor.when(StringNode) + def visit(self, node: StringNode, scope: Scope): + node.static_type = self.String_Type + + @visitor.when(BoolNode) + def visit(self, node: BoolNode, scope: Scope): + node.static_type = self.Bool_Type + + @visitor.when(IdNode) + def visit(self, node: IdNode, scope: Scope): + # Chequeo que la variable exista + if scope.is_defined(node.token.lex): + node.static_type = scope.find_variable(node.token.lex).type + else: + self.errors.append(NameError(node.line, node.column, + f'Undeclared identifier {node.token.lex}')) + node.static_type = ErrorType() diff --git a/src/core/semantic/TypeCollector.py b/src/core/semantic/TypeCollector.py new file mode 100644 index 000000000..bde0032a2 --- /dev/null +++ b/src/core/semantic/TypeCollector.py @@ -0,0 +1,38 @@ +from core.tools import visitor +from core.tools.Semantic import * +from core.parser.Parser import ProgramNode, ClassDeclarationNode +from core.tools.Errors import SemanticError + +# Visitor encargado de coleccionar los nombres de las clases que se definen +# en el codigo del programa COOL, chequea ademas que no se redeclaren e +# incluye los tipos builtin dentro del contexto + +class Type_Collector: + def __init__(self): + self.errors = [] + + self.Context = Context() + + self.Context.add_type(SelfType()) + + self.Context.create_type('Object', 0, 0) + self.Context.create_type('String', 0, 0) + self.Context.create_type('IO', 0, 0) + self.Context.create_type('Int', 0, 0) + self.Context.create_type('Bool', 0, 0) + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node : ProgramNode): + for type in node.declarations: + self.visit(type) + + @visitor.when(ClassDeclarationNode) + def visit(self, node : ClassDeclarationNode): + try: + self.Context.create_type(node.id.lex, node.line, node.column) + except SemanticException as ex: + self.errors.append(SemanticError(node.line, node.column, ex.text)) diff --git a/src/core/tools/Automata.py b/src/core/tools/Automata.py new file mode 100644 index 000000000..2bdadd2d1 --- /dev/null +++ b/src/core/tools/Automata.py @@ -0,0 +1,210 @@ +try: + import pydot +except: + pass + +# Esta es la clase dada en clases practicas +# para representar un DFA, +# o cualkier grafo en general +class State: + def __init__(self, state, final=False, formatter=lambda x: str(x), shape='circle'): + self.state = state + self.final = final + self.transitions = {} + self.epsilon_transitions = set() + self.tag = None + self.formatter = formatter + self.shape = shape + + # The method name is set this way from compatibility issues. + def set_formatter(self, value, attr='formatter', visited=None): + if visited is None: + visited = set() + elif self in visited: + return + + visited.add(self) + self.__setattr__(attr, value) + for destinations in self.transitions.values(): + for node in destinations: + node.set_formatter(value, attr, visited) + for node in self.epsilon_transitions: + node.set_formatter(value, attr, visited) + return self + + def has_transition(self, symbol): + return symbol in self.transitions + + def add_transition(self, symbol, state): + try: + self.transitions[symbol].append(state) + except: + self.transitions[symbol] = [state] + return self + + def add_epsilon_transition(self, state): + self.epsilon_transitions.add(state) + return self + + def recognize(self, string): + states = self.epsilon_closure + for symbol in string: + states = self.move_by_state(symbol, *states) + states = self.epsilon_closure_by_state(*states) + return any(s.final for s in states) + + def to_deterministic(self, formatter=lambda x: str(x)): + closure = self.epsilon_closure + start = State(tuple(closure), any(s.final for s in closure), formatter) + + closures = [ closure ] + states = [ start ] + pending = [ start ] + + while pending: + state = pending.pop() + symbols = { symbol for s in state.state for symbol in s.transitions } + + for symbol in symbols: + move = self.move_by_state(symbol, *state.state) + closure = self.epsilon_closure_by_state(*move) + + if closure not in closures: + new_state = State(tuple(closure), any(s.final for s in closure), formatter) + closures.append(closure) + states.append(new_state) + pending.append(new_state) + else: + index = closures.index(closure) + new_state = states[index] + + state.add_transition(symbol, new_state) + + return start + + @staticmethod + def from_nfa(nfa, get_states=False): + states = [] + for n in range(nfa.states): + state = State(n, n in nfa.finals) + states.append(state) + + for (origin, symbol), destinations in nfa.map.items(): + origin = states[origin] + origin[symbol] = [ states[d] for d in destinations ] + + if get_states: + return states[nfa.start], states + return states[nfa.start] + + @staticmethod + def move_by_state(symbol, *states): + return { s for state in states if state.has_transition(symbol) for s in state[symbol]} + + @staticmethod + def epsilon_closure_by_state(*states): + closure = { state for state in states } + + l = 0 + while l != len(closure): + l = len(closure) + tmp = [s for s in closure] + for s in tmp: + for epsilon_state in s.epsilon_transitions: + closure.add(epsilon_state) + return closure + + @property + def epsilon_closure(self): + return self.epsilon_closure_by_state(self) + + @property + def name(self): + return self.formatter(self.state) + + def get(self, symbol): + target = self.transitions[symbol] + assert len(target) == 1 + return target[0] + + def __getitem__(self, symbol): + if symbol == '': + return self.epsilon_transitions + try: + return self.transitions[symbol] + except KeyError: + return None + + def __setitem__(self, symbol, value): + if symbol == '': + self.epsilon_transitions = value + else: + self.transitions[symbol] = value + + def __repr__(self): + return str(self) + + def __str__(self): + return str(self.state) + + def __hash__(self): + return hash(self.state) + + def __iter__(self): + yield from self._visit() + + def _visit(self, visited=None): + if visited is None: + visited = set() + elif self in visited: + return + + visited.add(self) + yield self + + for destinations in self.transitions.values(): + for node in destinations: + yield from node._visit(visited) + for node in self.epsilon_transitions: + yield from node._visit(visited) + + def graph(self, dir = 'LR'): + G = pydot.Dot(rankdir=dir, margin=0.1) + G.add_node(pydot.Node('start', shape='plaintext', label='', width=0, height=0)) + + visited = set() + def visit(start): + ids = id(start) + if ids not in visited: + visited.add(ids) + G.add_node(pydot.Node(ids, label = f'\'{start.name}\'', shape=self.shape, style='bold' if start.final else '')) + for tran, destinations in start.transitions.items(): + for end in destinations: + visit(end) + G.add_edge(pydot.Edge(ids, id(end), label=tran, labeldistance=2)) + for end in start.epsilon_transitions: + visit(end) + G.add_edge(pydot.Edge(ids, id(end), label='ε', labeldistance=2)) + + visit(self) + G.add_edge(pydot.Edge('start', id(self), label='', style='dashed')) + + return G + + def _repr_svg_(self): + try: + return self.graph().create_svg().decode('utf8') + except: + pass + + def write_to(self, fname): + return self.graph().write_svg(fname) + +def multiline_formatter(state): + return '\n'.join(str(item) for item in state) + +def lr0_formatter(state): + try: + return '\n'.join(str(item)[:-4] for item in state) + except TypeError: + return str(state)[:-4] \ No newline at end of file diff --git a/src/core/tools/Errors.py b/src/core/tools/Errors.py new file mode 100644 index 000000000..4d1b8e0f1 --- /dev/null +++ b/src/core/tools/Errors.py @@ -0,0 +1,66 @@ + +class Error: + def __init__(self, line = None, column = None, text = ''): + self.line = line + self.column = column + self.text = text + def __str__(self): + raise NotImplementedError() + def __repr__(self): + raise NotImplementedError() + +class CompilerError(Error): + def __str__(self): + return f'{self.line, self.column} - ' \ + f'CompilerError: {self.text}' + def __repr__(self): + return f'{self.line, self.column} - ' \ + f'CompilerError: {self.text}' + +class LexicographicError(Error): + def __str__(self): + return f'{self.line, self.column} - ' \ + f'LexicographicError: {self.text}' + def __repr__(self): + return f'{self.line, self.column} - ' \ + f'LexicographicError: {self.text}' + +class SyntacticError(Error): + def __str__(self): + return f'{self.line, self.column} - ' \ + f'SyntacticError: ERROR at or near {self.text}' + def __repr__(self): + return f'{self.line, self.column} - ' \ + f'SyntacticError: ERROR at or near {self.text}' + +class SemanticError(Error): + def __str__(self): + return f'{self.line, self.column} - ' \ + f'SemanticError: {self.text}' + def __repr__(self): + return f'{self.line, self.column} - ' \ + f'SemanticError: {self.text}' + +class TypeError(Error): + def __str__(self): + return f'{self.line, self.column} - ' \ + f'TypeError: {self.text}' + def __repr__(self): + return f'{self.line, self.column} - ' \ + f'TypeError: {self.text}' + +class NameError(Error): + def __str__(self): + return f'{self.line, self.column} - ' \ + f'NameError: {self.text}' + def __repr__(self): + return f'{self.line, self.column} - ' \ + f'NameError: {self.text}' + +class AttributeError(Error): + def __str__(self): + return f'{self.line, self.column} - ' \ + f'AttributeError: {self.text}' + def __repr__(self): + return f'{self.line, self.column} - ' \ + f'AttributeError: {self.text}' \ No newline at end of file diff --git a/src/core/tools/Evaluation.py b/src/core/tools/Evaluation.py new file mode 100644 index 000000000..52cbd4958 --- /dev/null +++ b/src/core/tools/Evaluation.py @@ -0,0 +1,44 @@ +from .Parsing import ShiftReduceParser + +''' +Este metodo retorna el ast dado el conjunto de producciones, +las operaciones y los tokens +''' +def evaluate_reverse_parse(right_parse, operations, tokens): + if not right_parse or not operations or not tokens: + return + + right_parse = iter(right_parse) + tokens = iter(tokens) + stack = [] + for operation in operations: + # Si hay que hacer un shift, pasemos a analizar + # el proximo token e incluyamoslo en la pila + if operation == ShiftReduceParser.SHIFT: + token = next(tokens) + stack.append(token) + # Si hay que hacer un reduce, tomamos los elementos + # necesarios de la pila y los sustituimos por el nonterminal + # correspondiente, el cual incluimos en la pila + elif operation == ShiftReduceParser.REDUCE: + production = next(right_parse) + head, body = production + attributes = production.attributes + assert all(rule is None for rule in attributes[1:]), 'There must be only syntheticed attributes.' + rule = attributes[0] + + if len(body): + syntheticed = [None] + stack[-len(body):] + value = rule(None, syntheticed) + stack[-len(body):] = [value] + else: + stack.append(rule(None, None)) + else: + raise Exception('Invalid action!!!') + + # La pila debe terminar con el program node + # correspondiente a la raiz del ast, todos los + # tokens deben haber sido analizados + assert len(stack) == 1 + assert next(tokens).token_type == '$'#######estaba EOF, creo que tiene que ver con lo que cambie al poner el tokentype en el lexer + return stack[0] \ No newline at end of file diff --git a/src/core/tools/FirstsAndFollows.py b/src/core/tools/FirstsAndFollows.py new file mode 100644 index 000000000..1a8e05c61 --- /dev/null +++ b/src/core/tools/FirstsAndFollows.py @@ -0,0 +1,128 @@ +from .Utils import ContainerSet + +''' +Dada una forma oracional alpha computa sus firsts +''' +def compute_local_first(firsts, alpha): + first_alpha = ContainerSet() + + try: + alpha_is_epsilon = alpha.IsEpsilon + except: + alpha_is_epsilon = False + + # alpha == epsilon ? First(alpha) = { epsilon } + if alpha_is_epsilon: + first_alpha.set_epsilon() + return first_alpha + + # alpha = X1 ... XN + # First(Xi) subconjunto First(alpha) + # epsilon pertenece a First(X1)...First(Xi) ? First(Xi+1) subconjunto de First(X) y First(alpha) + # epsilon pertenece a First(X1)...First(XN) ? epsilon pertence a First(X) y al First(alpha) + for symbol in alpha: + first_alpha.update(firsts[symbol]) + if not firsts[symbol].contains_epsilon: + break + else: + first_alpha.set_epsilon() + + return first_alpha + + +''' +Computa los firsts de todos los simbolos de la gramatica +''' +def compute_firsts(G): + firsts = {} + change = True + + # Los firsts de los terminales son ellos mismos + for terminal in G.terminals: + firsts[terminal] = ContainerSet(terminal) + + # Inicializa los firsts de los noterminales como un conjunto vacio + for nonterminal in G.nonTerminals: + firsts[nonterminal] = ContainerSet() + + # Metodo de punto fijo, mientras halla algun cambio en los firsts + # de algun simbolo, itera por todas las producciones recalculando los firsts + # del noterminal correspondiente + while change: + change = False + + # P: X -> alpha + for production in G.Productions: + X = production.Left + alpha = production.Right + + # get current First(X) + first_X = firsts[X] + + # init First(alpha) + try: + first_alpha = firsts[alpha] + except KeyError: + first_alpha = firsts[alpha] = ContainerSet() + + # CurrentFirst(alpha) + local_first = compute_local_first(firsts, alpha) + + # update First(X) and First(alpha) from CurrentFirst(alpha) + change |= first_alpha.hard_update(local_first) + change |= first_X.hard_update(local_first) + + return firsts + +''' +Computa los follows de cada noterminal de la gramatica +''' +from itertools import islice +def compute_follows(G, firsts): + follows = {} + change = True + + local_firsts = {} + + # Inicializa los follows de los noterminales como un conjunto vacio + for nonterminal in G.nonTerminals: + follows[nonterminal] = ContainerSet() + # EOF pertenece a los follows del simbolo inicial + follows[G.startSymbol] = ContainerSet(G.EOF) + + # Metodo de punto fijo, mientras haya cambios en los follows + # de algun noterminal, itera por todas las producciones y recalcula + # los follows de cada noterminal + while change: + change = False + + # P: X -> alpha + for production in G.Productions: + X = production.Left + alpha = production.Right + + follow_X = follows[X] + + # Si la produccion actual es de la forma X -> v Y w + # donde v y w son formas oracionales entonces cada elemento + # que pertenece al first de w (excepto epsilon) pertenece tambien al follow + # de Y. Si ademas w ->* epsilon entonces los follows de X pertenecen + # al follows de Y. + # X -> zeta Y beta + # First(beta) - { epsilon } subset of Follow(Y) + # beta ->* epsilon or X -> zeta Y ? Follow(X) subset of Follow(Y) + for i, symbol in enumerate(alpha): + if symbol.IsNonTerminal: + try: + first_beta = local_firsts[alpha, i] + except KeyError: + first_beta = local_firsts[alpha, i] = compute_local_first(firsts, islice(alpha, i + 1, None)) + + change |= follows[symbol].update(first_beta) + + if first_beta.contains_epsilon: + change |= follows[symbol].update(follow_X) + + return follows + + diff --git a/src/core/tools/ParserLR1.py b/src/core/tools/ParserLR1.py new file mode 100644 index 000000000..1829ecca8 --- /dev/null +++ b/src/core/tools/ParserLR1.py @@ -0,0 +1,185 @@ +from .Pycompiler import Item +from .Utils import ContainerSet +from .FirstsAndFollows import compute_firsts, compute_local_first +from .Automata import State +from .Parsing import ShiftReduceParser + +''' +Recibe un item LR(1) y devuelve el conjunto de items que +sugiere incluir (directamente) debido a la presencia de +un . delante de un no terminal. +expand("𝑌→𝛼.𝑋𝛿,𝑐") = { "𝑋→.𝛽,𝑏" | 𝑏∈𝐹𝑖𝑟𝑠𝑡(𝛿𝑐) } +''' +def expand(item, firsts): + next_symbol = item.NextSymbol + if next_symbol is None or not next_symbol.IsNonTerminal: + return [] + + lookaheads = ContainerSet() + # (Compute lookahead for child items) + # Preview retorna los elementos que se encuantran detras del punto + # en el item mas sus lookaheads + for prev in item.Preview(): + lookaheads.update(compute_local_first(firsts, prev)) + + assert not lookaheads.contains_epsilon + + # (Build and return child items) + return [Item(x, 0, lookaheads) for x in next_symbol.productions] + +''' +Recibe un conjunto de items LR(1) y devuelve el mismo conjunto pero +en el que los items con mismo centro están unidos (se combinan los lookahead). +''' +def compress(items): + centers = {} + + for item in items: + center = item.Center() + try: + lookaheads = centers[center] + except KeyError: + centers[center] = lookaheads = set() + lookaheads.update(item.lookaheads) + + return {Item(x.production, x.pos, set(lookahead)) for x, lookahead in centers.items()} + +''' +Computa la clausura de un conjunto de items +''' +# Metodo de punto fijo, mientras haya cambios en el conjunto de items, +# itero por todos los items X -> v.Yw,p, donde X y Y son noterminales y +# v y w son formas oracionales, e incluyo los items de la forma +# Y -> Z,b donde b ∈ First(wp), para todas las producciones de Y. +# Dischos items se calculan mediantes el metodo expand. +# 𝐶𝐿(𝐼) = 𝐼 ∪ { 𝑋→.𝛽,𝑏 } +# tales que 𝑌→𝛼.𝑋𝛿,𝑐 ∈ 𝐶𝐿(𝐼) y 𝑏 ∈ 𝐹𝑖𝑟𝑠𝑡(𝛿𝑐) + +def closure_lr1(items, firsts): + closure = ContainerSet(*items) + + changed = True + while changed: + changed = False + + new_items = ContainerSet() + for item in closure: + new_items.extend(expand(item, firsts)) + + changed = closure.update(new_items) + + return compress(closure) + +''' +Recibe como parámetro un conjunto de items y un símbolo, y devuelve el +conjunto goto(items, symbol). El método permite setear el parámentro +just_kernel=True para calcular solamente el conjunto de items kernels +en lugar de todo el conjunto de items. En caso contrario, se debe proveer +el conjunto con los firsts de la gramática, puesto que serán usados al +calcular la clausura. +''' +# 𝐺𝑜𝑡𝑜(𝐼,𝑋) = 𝐶𝐿({ 𝑌→𝛼𝑋.𝛽,𝑐 | 𝑌→𝛼.𝑋𝛽,𝑐 ∈ 𝐼}) +def goto_lr1(items, symbol, firsts=None, just_kernel=False): + assert just_kernel or firsts is not None, '`firsts` must be provided if `just_kernel=False`' + items = frozenset(item.NextItem() for item in items if item.NextSymbol == symbol) + return items if just_kernel else closure_lr1(items, firsts) + +''' +Computa el automata LR1 correspondiente a la gramatica +''' +# El estado inicial es la clausura del item S' -> .S, $. +# Todos los estados son finales. +# Las transiciones ocurren con terminales y no terminales. +# La función de transición está dada por la función goto. +# f(Ii, c) = Goto(Ii, c) +def build_LR1_automaton(G): + assert len(G.startSymbol.productions) == 1, 'Grammar must be augmented' + + firsts = compute_firsts(G) + firsts[G.EOF] = ContainerSet(G.EOF) + + start_production = G.startSymbol.productions[0] + start_item = Item(start_production, 0, lookaheads=(G.EOF,)) + start = frozenset([start_item]) + + # El estado inicial es la clausura del item S' -> .S, $ + closure = closure_lr1(start, firsts) + automaton = State(frozenset(closure), True) + + pending = [start] + visited = {start: automaton} + + # BFS para construir el automata + # Mientras hallan estados pendientes + while pending: + # Tomo el siguiente estado a analizar + current = pending.pop() + current_state = visited[current] + + # Itero por cada simbolo de la gramatica + for symbol in G.terminals + G.nonTerminals: + # Chequeo si el estado actual posee transicion con ese simbolo a algun estado x + next_state_key = goto_lr1(current_state.state, symbol, just_kernel=True) + + # Si no la posee, continuo al siguiente simbolo + if not next_state_key: + continue + try: + next_state = visited[next_state_key] + except KeyError: + # Si el estado x no ha sido visto por el bfs, lo incluyo + # en la lista de pending + next_state_items = goto_lr1(current_state.state, symbol, firsts) + next_state = State(frozenset(next_state_items), True) + pending.append(next_state_key) + visited[next_state_key] = next_state + + # incluto la transicion del estado actual a x con el simbolo actual + current_state.add_transition(symbol.Name, next_state) + + #automaton.set_formatter(multiline_formatter) + return automaton + + +# Recordar que la diferencia entre los parsers Shift-Reduce es solo la forma +# en que se llenan las tablas ACTION y GOTO +class LR1Parser(ShiftReduceParser): + def _build_parsing_table(self): + G = self.G.AugmentedGrammar(True) + + automaton = build_LR1_automaton(G) + for i, node in enumerate(automaton): + node.idx = i + + for node in automaton: + idx = node.idx + for item in node.state: + # - Fill `self.Action` and `self.Goto` according to `item`) + # - Feel free to use `self._register(...)`) + if item.IsReduceItem: + if item.production.Left == G.startSymbol: + # Sea 𝐼𝑖 el estado que contiene el item "𝑆′→𝑆.,$" (𝑆′ distinguido). + # Entonces 𝐴𝐶𝑇𝐼𝑂𝑁[𝐼𝑖,$]=‘𝑂𝐾‘ + self._register(self.action, (idx, G.EOF.Name), (self.OK, None)) + else: + # Sea "𝑋→𝛼.,𝑠" un item del estado 𝐼𝑖. + # Entonces 𝐴𝐶𝑇𝐼𝑂𝑁[𝐼𝑖,𝑠]=‘𝑅𝑘‘ (producción k es 𝑋→𝛼) + for c in item.lookaheads: + self._register(self.action, (idx, c.Name), (self.REDUCE, item.production)) + else: + next_symbol = item.NextSymbol + try: + next_state = node[next_symbol.Name][0] + if next_symbol.IsNonTerminal: + # Sea "𝑋→𝛼.𝑌𝜔,𝑠" item del estado 𝐼𝑖 y 𝐺𝑜𝑡𝑜(𝐼𝑖,𝑌)=𝐼𝑗. + # Entonces 𝐺𝑂𝑇𝑂[𝐼𝑖,𝑌]=𝑗 + self._register(self.goto, (idx, next_symbol.Name), next_state.idx) + else: + # Sea "𝑋→𝛼.𝑐𝜔,𝑠" un item del estado 𝐼𝑖 y 𝐺𝑜𝑡𝑜(𝐼𝑖,𝑐)=𝐼𝑗. + # Entonces 𝐴𝐶𝑇𝐼𝑂𝑁[𝐼𝑖,𝑐]=‘𝑆𝑗‘ + self._register(self.action, (idx, next_symbol.Name), (self.SHIFT, next_state.idx)) + except KeyError: + print(f'Node: {node} without transition with symbol {next_symbol}') + return + + diff --git a/src/core/tools/Parsing.py b/src/core/tools/Parsing.py new file mode 100644 index 000000000..17eae0e2d --- /dev/null +++ b/src/core/tools/Parsing.py @@ -0,0 +1,95 @@ +from .Errors import * + +# Clase base para los parsers Shift-Reduce +class ShiftReduceParser: + SHIFT = 'SHIFT' + REDUCE = 'REDUCE' + OK = 'OK' + + def __init__(self, G, verbose=False): + self.G = G + self.verbose = verbose + self.action = {} + self.goto = {} + self.HasConflict = False + self.errors = [] + self._build_parsing_table() + + def _build_parsing_table(self): + raise NotImplementedError() + + ''' + Retorna las producciones y operaciones necesarias para el + proceso de parsing del string w. + ''' + def __call__(self, w): + stack = [0] + cursor = 0 + output = [] + operations = [] + self.errors = [] + + while True: + # Estado del automata en el que me encuentro + state = stack[-1] + # Siguiente simbolo a analizar + lookahead = w[cursor].token_type + if self.verbose: print(stack, '<---||--->', w[cursor:]) + #(Detect error) + try: + action, tag = self.action[state, lookahead][0] + except KeyError: + # Si no existe transicion desde el estado actual con el simbolo + # correspondiente es porque ha ocurrido un error + lookahead = w[cursor] + line = lookahead.line + column = lookahead.column + if lookahead.lex == '$': + self.errors.append(SyntacticError(0, 0, 'EOF')) + else: + self.errors.append(SyntacticError(line, column, f'"{lookahead.lex}"')) + return None, None + + # Si la accion es Shift, incluyo en la pila el simbolo actual y + # el estado al que necesito moverme + if action == self.SHIFT: + stack.append(lookahead) + stack.append(tag) + operations.append(self.SHIFT) + cursor += 1 + # Si la accion es Reduce, saco de la pila los simbolos correspondientes + # a la produccion q debo reducir + elif action == self.REDUCE: + output.append(tag) + for i in range(len(tag.Right)): + stack.pop() + assert stack[-1] == tag.Right[-i - 1].Name, 'Symbol does not match' + stack.pop() + # Tomo el estado al que debo moverme + index = self.goto[stack[-1], tag.Left.Name][0] + # Incluyo en la pila el simbolo reducido y el estado al + # que necesito moverme + stack.append(tag.Left.Name) + stack.append(index) + operations.append(self.REDUCE) + #(OK case) + elif action == self.OK: + return output, operations + #(Invalid case) + else: + lookahead = w[cursor] + line = lookahead.line + column = lookahead.column + if lookahead.lex == '$': + self.errors.append(SyntacticError(0, 0, 'EOF')) + else: + self.errors.append(SyntacticError(line, column, f'"{lookahead.lex}"')) + return None, None + + def _register(self, table, key, value): + if key not in table: + table[key] = [value] + elif not any(i for i in table[key] if i == value): + self.HasConflict = True + table[key].append(value) + diff --git a/src/core/tools/Pycompiler.py b/src/core/tools/Pycompiler.py new file mode 100644 index 000000000..74ad3bc0d --- /dev/null +++ b/src/core/tools/Pycompiler.py @@ -0,0 +1,560 @@ +import json + +#Clase base para los simbolos de la gramatica, terminales y noterminales +class Symbol(object): + + def __init__(self, name, grammar): + self.Name = name + self.Grammar = grammar + + def __str__(self): + return self.Name + + def __repr__(self): + return self.Name + + def __add__(self, other): + if isinstance(other, Symbol): + return Sentence(self, other) + + if isinstance(other, Sentence): + return Sentence(*((self,) + other._symbols)) + + raise TypeError(other) + + def __or__(self, other): + + if isinstance(other, (Sentence)): + return SentenceList(Sentence(self), other) + + raise TypeError(other) + + @property + def IsEpsilon(self): + return False + + def __len__(self): + return 1 + +class NonTerminal(Symbol): + + + def __init__(self, name, grammar): + super().__init__(name, grammar) + self.productions = [] + + + def __imod__(self, other): + + if isinstance(other, (Sentence)): + p = Production(self, other) + self.Grammar.Add_Production(p) + return self + + if isinstance(other, tuple): + assert len(other) > 1 + + if len(other) == 2: + other += (None,) * len(other[0]) + + assert len(other) == len(other[0]) + 2, "Debe definirse una, y solo una, regla por cada símbolo de la producción" + # assert len(other) == 2, "Tiene que ser una Tupla de 2 elementos (sentence, attribute)" + + if isinstance(other[0], Symbol) or isinstance(other[0], Sentence): + p = AttributeProduction(self, other[0], other[1:]) + else: + raise Exception("") + + self.Grammar.Add_Production(p) + return self + + if isinstance(other, Symbol): + p = Production(self, Sentence(other)) + self.Grammar.Add_Production(p) + return self + + if isinstance(other, SentenceList): + + for s in other: + p = Production(self, s) + self.Grammar.Add_Production(p) + + return self + + raise TypeError(other) + + def __add__(self, other): + if isinstance(other, Symbol): + return Sentence(self, other) + + if isinstance(other, Sentence): + return Sentence(self, *(other._symbols)) + + if other: + raise TypeError(other) + + def __iter__(self): + yield self + + @property + def IsTerminal(self): + return False + + @property + def IsNonTerminal(self): + return True + + @property + def IsEpsilon(self): + return False + +class Terminal(Symbol): + + def __init__(self, name, grammar): + super().__init__(name, grammar) + + def __iter__(self): + yield self + + def __add__(self, other): + if isinstance(other, Symbol): + return Sentence(self, other) + + if isinstance(other, Sentence): + return Sentence(self, *(other._symbols)) + + if other: + raise TypeError(other) + + @property + def IsTerminal(self): + return True + + @property + def IsNonTerminal(self): + return False + + @property + def IsEpsilon(self): + return False + +class EOF(Terminal): + + def __init__(self, Grammar): + super().__init__('$', Grammar) + +class Sentence(object): + + def __init__(self, *args): + self._symbols = tuple(x for x in args if not x.IsEpsilon) + self.hash = hash(self._symbols) + + def __len__(self): + return len(self._symbols) + + def __add__(self, other): + if isinstance(other, Symbol): + return Sentence(*(self._symbols + (other,))) + + if isinstance(other, Sentence): + return Sentence(*(self._symbols + other._symbols)) + + if other: + raise TypeError(other) + + def __or__(self, other): + if isinstance(other, Sentence): + return SentenceList(self, other) + + if isinstance(other, Symbol): + return SentenceList(self, Sentence(other)) + + if isinstance(other, SentenceList): + return SentenceList(self, *(other._sentences)) + + if other: + raise TypeError(other) + + def __repr__(self): + return str(self) + + def __str__(self): + return ("%s " * len(self._symbols) % tuple(self._symbols)).strip() + + def __iter__(self): + return iter(self._symbols) + + def __getitem__(self, index): + return self._symbols[index] + + def __eq__(self, other): + return self._symbols == other._symbols + + def __hash__(self): + return self.hash + + @property + def IsEpsilon(self): + return False + +class SentenceList(object): + + def __init__(self, *args): + self._sentences = list(args) + + def Add(self, symbol): + if not symbol and (symbol is None or not symbol.IsEpsilon): + raise ValueError(symbol) + + self._sentences.append(symbol) + + def __iter__(self): + return iter(self._sentences) + + def __or__(self, other): + if isinstance(other, Sentence): + self.Add(other) + return self + + if isinstance(other, Symbol): + return self | Sentence(other) + + if isinstance(other, SentenceList): + return SentenceList(self._sentences + other._sentences) + +class Epsilon(Terminal, Sentence): + + def __init__(self, grammar): + super().__init__('epsilon', grammar) + + + def __str__(self): + return "ε" + + def __repr__(self): + return 'epsilon' + + def __iter__(self): + yield from () + + def __len__(self): + return 0 + + def __add__(self, other): + return other + + def __eq__(self, other): + return isinstance(other, (Epsilon,)) + + def __hash__(self): + return hash("") + + @property + def IsEpsilon(self): + return True + +class Production(object): + + def __init__(self, nonTerminal, sentence): + + self.Left = nonTerminal + self.Right = sentence + + def __str__(self): + + return '%s --> %s' % (self.Left, self.Right) + + def __repr__(self): + return '%s -> %s' % (self.Left, self.Right) + + def __iter__(self): + yield self.Left + yield self.Right + + def __eq__(self, other): + return isinstance(other, Production) and self.Left == other.Left and self.Right == other.Right + + def __hash__(self): + return hash((self.Left, self.Right)) + + @property + def IsEpsilon(self): + return self.Right.IsEpsilon + +class AttributeProduction(Production): + + def __init__(self, nonTerminal, sentence, attributes): + if not isinstance(sentence, Sentence) and isinstance(sentence, Symbol): + sentence = Sentence(sentence) + super(AttributeProduction, self).__init__(nonTerminal, sentence) + + self.attributes = attributes + + def __str__(self): + return '%s := %s' % (self.Left, self.Right) + + def __repr__(self): + return '%s -> %s' % (self.Left, self.Right) + + def __iter__(self): + yield self.Left + yield self.Right + + + @property + def IsEpsilon(self): + return self.Right.IsEpsilon + + # sintetizar en ingles??????, pending aggrement + def syntetice(self): + pass + +class Grammar(): + + def __init__(self): + + self.Productions = [] + self.nonTerminals = [] + self.terminals = [] + self.startSymbol = None + # production type + self.pType = None + self.Epsilon = Epsilon(self) + self.EOF = EOF(self) + + self.symbDict = { '$': self.EOF } + + def NonTerminal(self, name, startSymbol = False): + + name = name.strip() + if not name: + raise Exception("Empty name") + + term = NonTerminal(name,self) + + if startSymbol: + + if self.startSymbol is None: + self.startSymbol = term + else: + raise Exception("Cannot define more than one start symbol.") + + self.nonTerminals.append(term) + self.symbDict[name] = term + return term + + def NonTerminals(self, names): + + ans = tuple((self.NonTerminal(x) for x in names.strip().split())) + + return ans + + + def Add_Production(self, production): + + if len(self.Productions) == 0: + self.pType = type(production) + + assert type(production) == self.pType, "The Productions most be of only 1 type." + + production.Left.productions.append(production) + self.Productions.append(production) + + + def Terminal(self, name): + + name = name.strip() + if not name: + raise Exception("Empty name") + + term = Terminal(name, self) + self.terminals.append(term) + self.symbDict[name] = term + return term + + def Terminals(self, names): + + ans = tuple((self.Terminal(x) for x in names.strip().split())) + + return ans + + + def __str__(self): + + mul = '%s, ' + + ans = 'Non-Terminals:\n\t' + + nonterminals = mul * (len(self.nonTerminals)-1) + '%s\n' + + ans += nonterminals % tuple(self.nonTerminals) + + ans += 'Terminals:\n\t' + + terminals = mul * (len(self.terminals)-1) + '%s\n' + + ans += terminals % tuple(self.terminals) + + ans += 'Productions:\n\t' + + ans += str(self.Productions) + + return ans + + def __getitem__(self, name): + try: + return self.symbDict[name] + except KeyError: + return None + + @property + def to_json(self): + + productions = [] + + for p in self.Productions: + head = p.Left.Name + + body = [] + + for s in p.Right: + body.append(s.Name) + + productions.append({'Head':head, 'Body':body}) + + d={'NonTerminals':[symb.Name for symb in self.nonTerminals], 'Terminals': [symb.Name for symb in self.terminals],\ + 'Productions':productions} + + # [{'Head':p.Left.Name, "Body": [s.Name for s in p.Right]} for p in self.Productions] + return json.dumps(d) + + @staticmethod + def from_json(data): + data = json.loads(data) + + G = Grammar() + dic = {'epsilon':G.Epsilon} + + for term in data['Terminals']: + dic[term] = G.Terminal(term) + + for noTerm in data['NonTerminals']: + dic[noTerm] = G.NonTerminal(noTerm) + + for p in data['Productions']: + head = p['Head'] + dic[head] %= Sentence(*[dic[term] for term in p['Body']]) + + return G + + def copy(self): + G = Grammar() + G.pType = self.pType + for terminal in self.terminals: + G.Terminal(terminal.Name) + for nonterminal in self.nonTerminals: + G.NonTerminal(nonterminal.Name, nonterminal == self.startSymbol) + for prod in self.Productions: + left = G.symbDict[prod.Left.Name] + if prod.IsEpsilon: + if self.pType is AttributeProduction: + G.Add_Production(AttributeProduction(left, G.Epsilon, prod.attributes)) + else: + G.Add_Production(Production(left, G.Epsilon)) + else: + right = Sentence() + for symbol in prod.Right: + right = right + G.symbDict[symbol.Name] + if self.pType is AttributeProduction: + G.Add_Production(AttributeProduction(left, right, prod.attributes)) + else: + G.Add_Production(Production(left, right)) + return G + + @property + def IsAugmentedGrammar(self): + augmented = 0 + for left, right in self.Productions: + if self.startSymbol == left: + augmented += 1 + if augmented <= 1: + return True + else: + return False + + def AugmentedGrammar(self, force=False): + if not self.IsAugmentedGrammar or force: + + G = self.copy() + # S, self.startSymbol, SS = self.startSymbol, None, self.NonTerminal('S\'', True) + S = G.startSymbol + G.startSymbol = None + SS = G.NonTerminal('S\'', True) + if G.pType is AttributeProduction: + SS %= S + G.Epsilon, lambda x : x + else: + SS %= S + G.Epsilon + + return G + else: + return self.copy() + #endchange + +class Item: + + def __init__(self, production, pos, lookaheads=[]): + self.production = production + self.pos = pos + self.lookaheads = frozenset(look for look in lookaheads) + + def __str__(self): + s = str(self.production.Left) + " -> " + if len(self.production.Right) > 0: + for i,c in enumerate(self.production.Right): + if i == self.pos: + s += "." + s += str(self.production.Right[i]) + if self.pos == len(self.production.Right): + s += "." + else: + s += "." + s += ", " + str(self.lookaheads)[10:-1] + return s + + def __repr__(self): + return str(self) + + + def __eq__(self, other): + return ( + (self.pos == other.pos) and + (self.production == other.production) and + (set(self.lookaheads) == set(other.lookaheads)) + ) + + def __hash__(self): + return hash((self.production,self.pos,self.lookaheads)) + + @property + def IsReduceItem(self): + return len(self.production.Right) == self.pos + + @property + def NextSymbol(self): + if self.pos < len(self.production.Right): + return self.production.Right[self.pos] + else: + return None + + def NextItem(self): + if self.pos < len(self.production.Right): + return Item(self.production,self.pos+1,self.lookaheads) + else: + return None + + def Preview(self, skip=1): + unseen = self.production.Right[self.pos+skip:] + return [ unseen + (lookahead,) for lookahead in self.lookaheads ] + + def Center(self): + return Item(self.production, self.pos) \ No newline at end of file diff --git a/src/core/tools/Semantic.py b/src/core/tools/Semantic.py new file mode 100644 index 000000000..10006a697 --- /dev/null +++ b/src/core/tools/Semantic.py @@ -0,0 +1,296 @@ +# Aqui estan las clases necesarias para representar los tipos del programa + +# Necesario para enviar errores semanticos +class SemanticException(Exception): + @property + def text(self): + return self.args[0] + + +# Representa un atributo en un tipo del programa +class Attribute: + def __init__(self, name : str, type, line : int, column : int, expression = None): + self.name = name + self.type = type + self.line = line + self.column = column + + def __str__(self): + return f'[attrib] {self.name}: {self.type.name};' + + def __repr__(self): + return str(self) + + def __lt__(self, other): + return self.name < other.name + +# Representa un metodo en un tipo del programa +class Method: + def __init__(self, name : str, param_names : list, param_types : list, return_type): + self.name = name + self.param_names = param_names + self.param_types = param_types + self.return_type = return_type + + def __str__(self): + params = ', '.join(f'{n}: {t.name}' for n, t in zip(self.param_names, self.param_types)) + return f'[method] {self.name}({params}): {self.return_type.name};' + + def __eq__(self, other): + return other.name == self.name and \ + other.return_type == self.return_type and \ + other.param_types == self.param_types + +#Clase base para representar los tipos del programa +class Type: + def __init__(self, name:str, line, column, sealed=False): + self.name = name + self.attributes = [] + self.methods = {} + self.parent = None + # Profundidad en el arbol de herencia + self.depth = 0 + # True si la clase esta sellada (no se puede heredar de ella) + self.sealed = sealed + self.line = line + self.column = column + + def set_parent(self, parent): + # Si el padre esta definido o es un tipo sellado entonces hay un error semantico + if self.parent is not None: + raise SemanticException(f'Parent type is already set for {self.name}.') + if parent.sealed: + raise SemanticException(f'Cannot inherit from sealed type {parent.name}') + self.parent = parent + + # Retorna el tipo en el arbol de herencia de mayor profundidad + # que es padre comun de ambos tipos + def type_union(self, other): + if self is VoidType or other is VoidType: + return VoidType() + + if self is ErrorType or other is ErrorType: + return ErrorType() + + x, y = self, other + while x.depth > y.depth: + x = x.parent + while y.depth > x.depth: + y = y.parent + + while x and x != y: + x = x.parent + y = y.parent + + return x + + # Retorna el atributo del tipo actual con el nombre correspondiente + def get_attribute(self, name:str): + try: + return next(attr for attr in self.attributes if attr.name == name) + except StopIteration: + if self.parent is None: + raise SemanticException(f'Attribute "{name}" is not defined in {self.name}.') + try: + return self.parent.get_attribute(name) + except SemanticException: + raise SemanticException(f'Attribute "{name}" is not defined in {self.name}.') + + # Define un atributo para el tipo actual con el nombre y el tipo correspondiente + def define_attribute(self, name:str, typex, line, column): + try: + attribute = next(attr for attr in self.attributes if attr.name == name) + except StopIteration: + attribute = Attribute(name, typex, line, column) + self.attributes.append(attribute) + return attribute + else: + raise SemanticException(f'Attribute "{name}" is multiply defined in class.') + + # Retorna el metodo del tipo actual con el nombre correspondiente + def get_method(self, name:str): + try: + return self.methods[name] + except KeyError: + if self.parent is None: + raise SemanticException(f'Method "{name}" is not defined in {self.name}.') + try: + return self.parent.get_method(name) + except SemanticException: + raise SemanticException(f'Method "{name}" is not defined in {self.name}.') + + # Define un metodo para el tipo actual con el nombre, los parametros y el tipo de + # retorno correspondientes + def define_method(self, name:str, param_names:list, param_types:list, return_type, line, column): + if name in self.methods: + raise SemanticException(f'Method {name} is multiply defined') + + method = self.methods[name] = Method(name, param_names, param_types, return_type) + method.return_info = VariableInfo(f'{self.name}_{name}_returnType', return_type) + + return method + + # Returns true if other is a parent of current type + def conforms_to(self, other): + return other.is_special_type() \ + or self.name == other.name \ + or (self.parent is not None and self.parent.conforms_to(other)) + + def is_special_type(self): + return False + + def is_void_type(self): + return False + + def __str__(self): + output = f'type {self.name}' + parent = '' if self.parent is None else f' : {self.parent.name}' + output += parent + output += ' {' + output += '\n\t' if self.attributes or self.methods else '' + output += '\n\t'.join(str(x) for x in self.attributes) + output += '\n\t' if self.attributes else '' + output += '\n\t'.join(str(x) for x in self.methods.values()) + output += '\n' if self.methods else '' + output += '}\n' + return output + + def __repr__(self): + return str(self) + +# Special_Types +class SelfType(Type): + def __init__(self): + Type.__init__(self, 'SELF_TYPE', 0, 0) + self.sealed = True + + def conforms_to(self, other): + return False + + def is_special_type(self): + return True + + def __eq__(self, other): + return isinstance(other, SelfType) + +class VoidType(Type): + def __init__(self): + Type.__init__(self, 'VOID_TYPE', 0, 0) + self.sealed = True + + def type_union(self, other): + return self + + def conforms_to(self, other): + return True + + def is_special_type(self): + return True + + def is_void_type(self): + return True + + def __eq__(self, other): + return isinstance(other, Type) + +class ErrorType(Type): + def __init__(self): + Type.__init__(self, '', 0, 0) + self.sealed = True + + def type_union(self, other): + return self + + def conforms_to(self, other): + return True + + def is_special_type(self): + return True + + def __eq__(self, other): + return isinstance(other, Type) + +# Clase para representar una variable dentro del programa +class VariableInfo: + def __init__(self, name, vtype): + self.name = name + self.type = vtype + + +# Clase para representar el contexto en el que se guardan las variables y los +# tipos durante el chequeo semantico del programa +class Context: + def __init__(self): + self.types = {} + + # Incluye un tipo dentro del contexto + def add_type(self, type : Type): + if type.name in self.types: + raise SemanticException('Classes may not be redefined') + self.types[type.name] = type + return type + + # Crea un tipo dentro del contexto + def create_type(self, name : str, line, column): + if name in self.types: + raise SemanticException('Classes may not be redefined') + type = self.types[name] = Type(name, line, column) + return type + + # Obtiene un tipo del contexto con el nombre correspondiente + def get_type(self, name : str): + try: + return self.types[name] + except KeyError: + raise SemanticException(f'Type {name} not defined.') + + def subtree(self, name : str): + type = self.get_type(name) + return (i for i in self.types.values() if i.conforms_to(type)) + + def __str__(self): + return '{\n\t' + '\n\t'.join(y for x in self.types.values() for y in str(x).split('\n')) + '\n}' + + def __repr__(self): + return str(self) + + +# Clase para representar el scope durante el chequeo semantico +class Scope: + def __init__(self, parent = None): + self.parent = parent + self.locals = [] + self.childs : [Scope] = [] + + # Crea un scope hijo + def create_child(self): + self.childs.append(Scope(self)) + return self.childs[-1] + + # Define una variable en el scope + def define_variable(self, name : str, type : Type, line : int, column : int): + self.locals.append(VariableInfo(name, type)) + return self.locals[-1] + + # Retorna una variable definida en el scope, None si no esta definida + def find_variable(self, name : str): + try: + return next(i for i in self.locals if i.name == name) + except StopIteration: + return self.parent.find_variable(name) if self.parent is not None else None + + # True si la variable esta definida en el scope + def is_defined(self, name : str): + return self.find_variable(name) is not None + + # True si la variable fue definida en el scope actual y no en alguno + # de sus scope padres + def is_local(self, name : str): + return any(i for i in self.locals if i.name == name) + + def __iter__(self): + for var in self.locals: + yield var + for ch in self.childs: + for var in ch: + yield var diff --git a/src/core/tools/Utils.py b/src/core/tools/Utils.py new file mode 100644 index 000000000..f8dcc227c --- /dev/null +++ b/src/core/tools/Utils.py @@ -0,0 +1,221 @@ +from .Pycompiler import Production, Sentence, Symbol, EOF, Epsilon + +class ContainerSet: + def __init__(self, *values, contains_epsilon=False): + self.set = set(values) + self.contains_epsilon = contains_epsilon + + def add(self, value): + n = len(self.set) + self.set.add(value) + return n != len(self.set) + + def extend(self, values): + change = False + for value in values: + change |= self.add(value) + return change + + def set_epsilon(self, value=True): + last = self.contains_epsilon + self.contains_epsilon = value + return last != self.contains_epsilon + + def update(self, other): + n = len(self.set) + self.set.update(other.set) + return n != len(self.set) + + def epsilon_update(self, other): + return self.set_epsilon(self.contains_epsilon | other.contains_epsilon) + + def hard_update(self, other): + return self.update(other) | self.epsilon_update(other) + + def find_match(self, match): + for item in self.set: + if item == match: + return item + return None + + def __len__(self): + return len(self.set) + int(self.contains_epsilon) + + def __str__(self): + return '%s-%s' % (str(self.set), self.contains_epsilon) + + def __repr__(self): + return str(self) + + def __iter__(self): + return iter(self.set) + + def __nonzero__(self): + return len(self) > 0 + + def __eq__(self, other): + if isinstance(other, set): + return self.set == other + return isinstance(other, ContainerSet) and self.set == other.set and self.contains_epsilon == other.contains_epsilon + + +def inspect(item, grammar_name='G', mapper=None): + try: + return mapper[item] + except (TypeError, KeyError ): + if isinstance(item, dict): + items = ',\n '.join(f'{inspect(key, grammar_name, mapper)}: {inspect(value, grammar_name, mapper)}' for key, value in item.items() ) + return f'{{\n {items} \n}}' + elif isinstance(item, ContainerSet): + args = f'{ ", ".join(inspect(x, grammar_name, mapper) for x in item.set) } ,' if item.set else '' + return f'ContainerSet({args} contains_epsilon={item.contains_epsilon})' + elif isinstance(item, EOF): + return f'{grammar_name}.EOF' + elif isinstance(item, Epsilon): + return f'{grammar_name}.Epsilon' + elif isinstance(item, Symbol): + return f"G['{item.Name}']" + elif isinstance(item, Sentence): + items = ', '.join(inspect(s, grammar_name, mapper) for s in item._symbols) + return f'Sentence({items})' + elif isinstance(item, Production): + left = inspect(item.Left, grammar_name, mapper) + right = inspect(item.Right, grammar_name, mapper) + return f'Production({left}, {right})' + elif isinstance(item, tuple) or isinstance(item, list): + ctor = ('(', ')') if isinstance(item, tuple) else ('[',']') + return f'{ctor[0]} {("%s, " * len(item)) % tuple(inspect(x, grammar_name, mapper) for x in item)}{ctor[1]}' + else: + raise ValueError(f'Invalid: {item}') + +def pprint(item, header=""): + if header: + print(header) + + if isinstance(item, dict): + for key, value in item.items(): + print(f'{key} ---> {value}') + elif isinstance(item, list): + print('[') + for x in item: + print(f' {repr(x)}') + print(']') + else: + print(item) + +class Token: + """ + Basic token class. + + Parameters + ---------- + lex : str + Token's lexeme. + token_type : Enum + Token's type. + """ + + def __init__(self, lex, token_type, line = None, column = None): + self.lex = lex + self.token_type = token_type + self.line = line + self.column = column + + def __str__(self): + return str(self.token_type) + + def __repr__(self): + return repr(self.token_type) + + @property + def is_valid(self): + return True + +class UnknownToken(Token): + def __init__(self, lex): + Token.__init__(self, lex, None) + + def transform_to(self, token_type): + return Token(self.lex, token_type) + + @property + def is_valid(self): + return False + +def tokenizer(G, fixed_tokens): + def decorate(func): + def tokenize_text(text): + tokens = [] + for lex in text.split(): + try: + token = fixed_tokens[lex] + except KeyError: + token = UnknownToken(lex) + try: + token = func(token) + except TypeError: + pass + tokens.append(token) + tokens.append(Token('$', G.EOF)) + return tokens + + if hasattr(func, '__call__'): + return tokenize_text + elif isinstance(func, str): + return tokenize_text(func) + else: + raise TypeError('Argument must be "str" or a callable object.') + return decorate + +class DisjointSet: + def __init__(self, *items): + self.nodes = { x: DisjointNode(x) for x in items } + + def merge(self, items): + items = (self.nodes[x] for x in items) + try: + head, *others = items + for other in others: + head.merge(other) + except ValueError: + pass + + @property + def representatives(self): + return { n.representative for n in self.nodes.values() } + + @property + def groups(self): + return [[n for n in self.nodes.values() if n.representative == r] for r in self.representatives] + + def __len__(self): + return len(self.representatives) + + def __getitem__(self, item): + return self.nodes[item] + + def __str__(self): + return str(self.groups) + + def __repr__(self): + return str(self) + +class DisjointNode: + def __init__(self, value): + self.value = value + self.parent = self + + @property + def representative(self): + if self.parent != self: + self.parent = self.parent.representative + return self.parent + + def merge(self, other): + other.representative.parent = self.representative + + def __str__(self): + return str(self.value) + + def __repr__(self): + return str(self) \ No newline at end of file diff --git a/src/core/tools/visitor.py b/src/core/tools/visitor.py new file mode 100644 index 000000000..964842836 --- /dev/null +++ b/src/core/tools/visitor.py @@ -0,0 +1,80 @@ +# The MIT License (MIT) +# +# Copyright (c) 2013 Curtis Schlak +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import inspect + +__all__ = ['on', 'when'] + +def on(param_name): + def f(fn): + dispatcher = Dispatcher(param_name, fn) + return dispatcher + return f + + +def when(param_type): + def f(fn): + frame = inspect.currentframe().f_back + func_name = fn.func_name if 'func_name' in dir(fn) else fn.__name__ + dispatcher = frame.f_locals[func_name] + if not isinstance(dispatcher, Dispatcher): + dispatcher = dispatcher.dispatcher + dispatcher.add_target(param_type, fn) + def ff(*args, **kw): + return dispatcher(*args, **kw) + ff.dispatcher = dispatcher + return ff + return f + + +class Dispatcher(object): + def __init__(self, param_name, fn): + frame = inspect.currentframe().f_back.f_back + top_level = frame.f_locals == frame.f_globals + self.param_index = self.__argspec(fn).args.index(param_name) + self.param_name = param_name + self.targets = {} + + def __call__(self, *args, **kw): + typ = args[self.param_index].__class__ + d = self.targets.get(typ) + if d is not None: + return d(*args, **kw) + else: + issub = issubclass + t = self.targets + ks = t.keys() + ans = [t[k](*args, **kw) for k in ks if issub(typ, k)] + if len(ans) == 1: + return ans.pop() + return ans + + def add_target(self, typ, target): + self.targets[typ] = target + + @staticmethod + def __argspec(fn): + # Support for Python 3 type hints requires inspect.getfullargspec + if hasattr(inspect, 'getfullargspec'): + return inspect.getfullargspec(fn) + else: + return inspect.getargspec(fn) diff --git a/tests/lexer/iis1.cl b/tests/lexer/iis1.cl index 12cb52beb..d5a4a096d 100644 --- a/tests/lexer/iis1.cl +++ b/tests/lexer/iis1.cl @@ -1,6 +1,6 @@ (* Integers, Identifiers, and Special Notation *) -0007 123 +1 -1 +90 -09 +11113 -4r *a *self* c++ +0007 123 +1 -1 +90 -09 +11113 -4r *a *self* c++ 5! = 120, 2 + 2 = 5 or E = mc2; p + 1 @ p = 1: for x in range(len(b)) new / <- <<==> {( Int: Objet, Bool; String.string SELF_TYPE isvoid }) class Class if then else fi testing Testing ~007agent_bond james_007B0N3SS___ diff --git a/tests/lexer/iis1_error.txt b/tests/lexer/iis1_error.txt index 9e6d66cac..a1d71cc56 100644 --- a/tests/lexer/iis1_error.txt +++ b/tests/lexer/iis1_error.txt @@ -1 +1 @@ -(4, 2) - LexicographicError: ERROR "!" +(4, 2) - LexicographicError: ERROR "!" \ No newline at end of file diff --git a/tests/lexer_test.py b/tests/lexer_test.py index 2a27223d3..f492b1959 100644 --- a/tests/lexer_test.py +++ b/tests/lexer_test.py @@ -10,4 +10,4 @@ @pytest.mark.run(order=1) @pytest.mark.parametrize("cool_file", tests) def test_lexer_errors(compiler_path, cool_file): - compare_errors(compiler_path, tests_dir + cool_file, tests_dir + cool_file[:-3] + '_error.txt') \ No newline at end of file + compare_errors(compiler_path, tests_dir + cool_file, tests_dir + cool_file[:-3] + '_error.txt') diff --git a/tests/utils/utils.py b/tests/utils/utils.py index 961cf7cbc..4e7a35363 100644 --- a/tests/utils/utils.py +++ b/tests/utils/utils.py @@ -45,7 +45,7 @@ def get_file_name(path: str): def compare_errors(compiler_path: str, cool_file_path: str, error_file_path: str, cmp=first_error, timeout=100): try: - sp = subprocess.run(['bash', compiler_path, cool_file_path], capture_output=True, timeout=timeout) + sp = subprocess.run(['bash', compiler_path, cool_file_path], stdout=subprocess.PIPE, timeout=timeout) return_code, output = sp.returncode, sp.stdout.decode() except subprocess.TimeoutExpired: assert False, COMPILER_TIMEOUT @@ -66,8 +66,9 @@ def compare_errors(compiler_path: str, cool_file_path: str, error_file_path: str See the file README for a full copyright notice\. (?:Loaded: .+\n)*''' def compare_outputs(compiler_path: str, cool_file_path: str, input_file_path: str, output_file_path: str, timeout=100): + print(compiler_path, cool_file_path, input_file_path, output_file_path) try: - sp = subprocess.run(['bash', compiler_path, cool_file_path], capture_output=True, timeout=timeout) + sp = subprocess.run(['bash', compiler_path, cool_file_path], timeout=timeout) assert sp.returncode == 0, TEST_MUST_COMPILE % get_file_name(cool_file_path) except subprocess.TimeoutExpired: assert False, COMPILER_TIMEOUT @@ -76,7 +77,7 @@ def compare_outputs(compiler_path: str, cool_file_path: str, input_file_path: st try: fd = open(input_file_path, 'rb') - sp = subprocess.run(['spim', '-file', spim_file], input=fd.read(), capture_output=True, timeout=timeout) + sp = subprocess.run(['spim', '-file', spim_file], input=fd.read(), timeout=timeout, capture_output=True) fd.close() mo = re.match(SPIM_HEADER, sp.stdout.decode()) if mo: